diff --git a/.eslintrc.json b/.eslintrc.json index e89e373216..5b9ae30f06 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -21,7 +21,7 @@ }, "overrides": [ { - "files": ["./*.js", "scripts/*.js"], + "files": ["./*.{js,ts}", "scripts/*.js"], "rules": { "import/no-extraneous-dependencies": [ "error", diff --git a/backend/.eslintrc.json b/backend/.eslintrc.json index 71ecb4c80a..68caf83132 100644 --- a/backend/.eslintrc.json +++ b/backend/.eslintrc.json @@ -1,8 +1,17 @@ { "extends": [ - "../node_modules/cloudify-ui-common/configs/eslint-common-node.json" + "plugin:node/recommended-module", + "../.eslintrc.temp-overrides.json" ], "rules": { - "prefer-promise-reject-errors": "off" - } + "prefer-promise-reject-errors": "off", + "node/no-missing-require": ["error", { + "tryExtensions": [".js", ".ts", ".json"] + }], + "node/no-missing-import": ["error", {"tryExtensions": [".js", ".json", ".ts"]}] + }, + "overrides": [{ + "files": "**/*.js", + "extends": ["../node_modules/cloudify-ui-common/configs/eslint-common-node.json"] + }] } diff --git a/backend/__mocks__/passport.ts b/backend/__mocks__/passport.ts new file mode 100644 index 0000000000..cda2cf99d1 --- /dev/null +++ b/backend/__mocks__/passport.ts @@ -0,0 +1,13 @@ +import type { RequestHandler } from 'express'; +import { noop } from 'lodash'; + +const middlewareMock: RequestHandler = (req, _res, next) => { + req.user = { username: 'testuser' }; + next(); +}; + +export default { + authenticate: () => middlewareMock, + initialize: () => middlewareMock, + use: noop +}; diff --git a/backend/app.js b/backend/app.ts similarity index 69% rename from backend/app.js rename to backend/app.ts index 6c183ab168..a802b06dcc 100644 --- a/backend/app.js +++ b/backend/app.ts @@ -1,43 +1,44 @@ -const fs = require('fs'); -const path = require('path'); -const expressStaticGzip = require('express-static-gzip'); -const express = require('express'); -const passport = require('passport'); -const cookieParser = require('cookie-parser'); -const morgan = require('morgan'); - -const config = require('./config'); -const Consts = require('./consts'); -const LoggerHandler = require('./handler/LoggerHandler'); -const ServerSettings = require('./serverSettings'); -const { getResourcePath } = require('./utils'); - -const getCookieStrategy = require('./auth/CookieStrategy'); -const getTokenStrategy = require('./auth/TokenStrategy'); -const getSamlStrategy = require('./auth/SamlStrategy'); -const samlSetup = require('./samlSetup'); -const Auth = require('./routes/Auth'); - -const Applications = require('./routes/Applications'); -const BlueprintAdditions = require('./routes/BlueprintAdditions'); -const BlueprintUserData = require('./routes/BlueprintUserData'); -const ClientConfig = require('./routes/ClientConfig'); -const External = require('./routes/External'); -const File = require('./routes/File'); -const GitHub = require('./routes/GitHub'); -const Maps = require('./routes/Maps'); -const Plugins = require('./routes/Plugins'); -const ServerProxy = require('./routes/ServerProxy'); -const SourceBrowser = require('./routes/SourceBrowser'); -const Style = require('./routes/Style'); -const Templates = require('./routes/Templates'); -const UserApp = require('./routes/UserApp'); -const WidgetBackend = require('./routes/WidgetBackend'); -const Widgets = require('./routes/Widgets'); -const Filters = require('./routes/Filters'); +// @ts-nocheck File not migrated fully to TS +import fs from 'fs'; +import path from 'path'; +import expressStaticGzip from 'express-static-gzip'; +import express from 'express'; +import passport from 'passport'; +import cookieParser from 'cookie-parser'; +import morgan from 'morgan'; + +import { getConfig, getClientConfig } from './config'; +import { CONTEXT_PATH } from './consts'; +import LoggerHandler from './handler/LoggerHandler'; +import { getMode } from './serverSettings'; +import { getResourcePath } from './utils'; + +import getCookieStrategy from './auth/CookieStrategy'; +import getTokenStrategy from './auth/TokenStrategy'; +import getSamlStrategy from './auth/SamlStrategy'; +import validateSamlConfig from './samlSetup'; +import Auth from './routes/Auth'; + +import Applications from './routes/Applications'; +import BlueprintAdditions from './routes/BlueprintAdditions'; +import BlueprintUserData from './routes/BlueprintUserData'; +import ClientConfig from './routes/ClientConfig'; +import External from './routes/External'; +import File from './routes/File'; +import GitHub from './routes/GitHub'; +import Maps from './routes/Maps'; +import Plugins from './routes/Plugins'; +import ServerProxy from './routes/ServerProxy'; +import SourceBrowser from './routes/SourceBrowser'; +import Style from './routes/Style'; +import Templates from './routes/Templates'; +import UserApp from './routes/UserApp'; +import WidgetBackend from './routes/WidgetBackend'; +import Widgets from './routes/Widgets'; +import Filters from './routes/Filters'; const logger = LoggerHandler.getLogger('App'); -const contextPath = Consts.CONTEXT_PATH; +const contextPath = CONTEXT_PATH; const oldContextPath = '/stage'; const app = express(); @@ -66,9 +67,9 @@ app.use(contextPath, (req, res, next) => { next(); }); -const samlConfig = config.get().app.saml; +const samlConfig = getConfig().app.saml; if (samlConfig.enabled) { - samlSetup.validate(samlConfig); + validateSamlConfig(samlConfig); passport.use(getSamlStrategy()); } @@ -116,7 +117,7 @@ app.use(`${contextPath}/github`, GitHub); app.use(`${contextPath}/external`, External); app.use(`${contextPath}/file`, File); app.use(`${contextPath}/config`, (req, res) => { - res.send(config.getForClient(ServerSettings.settings.mode)); + res.send(getClientConfig(getMode())); }); app.use(`${contextPath}/wb`, WidgetBackend); app.use(`${contextPath}/plugins`, Plugins); @@ -155,4 +156,4 @@ app.use((err, req, res, next) => { res.status(err.status || 404).send({ message: message || err }); }); -module.exports = app; +export default app; diff --git a/backend/auth/CookieStrategy.js b/backend/auth/CookieStrategy.js deleted file mode 100644 index 782aedbdd2..0000000000 --- a/backend/auth/CookieStrategy.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Created by jakub.niezgoda on 13/07/2018. - */ - -const CookieStrategy = require('passport-cookie').Strategy; -const AuthHandler = require('../handler/AuthHandler'); -const Consts = require('../consts'); - -module.exports = () => { - return new CookieStrategy({ cookieName: Consts.TOKEN_COOKIE_NAME }, (token, done) => - AuthHandler.getUser(token) - .then(user => done(null, user)) - .catch(err => done(null, false, err + token)) - ); -}; diff --git a/backend/auth/CookieStrategy.ts b/backend/auth/CookieStrategy.ts new file mode 100644 index 0000000000..8df70c7c10 --- /dev/null +++ b/backend/auth/CookieStrategy.ts @@ -0,0 +1,12 @@ +// @ts-nocheck File not migrated fully to TS +import { Strategy } from 'passport-cookie'; +import { getUser } from '../handler/AuthHandler'; +import { TOKEN_COOKIE_NAME } from '../consts'; + +export default () => { + return new Strategy({ cookieName: TOKEN_COOKIE_NAME }, (token, done) => + getUser(token) + .then(user => done(null, user)) + .catch(err => done(null, false, err + token)) + ); +}; diff --git a/backend/auth/SamlStrategy.js b/backend/auth/SamlStrategy.js deleted file mode 100644 index d213edc32f..0000000000 --- a/backend/auth/SamlStrategy.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Created by edenp on 7/30/17. - */ - -const SamlStrategy = require('passport-saml').Strategy; -const fs = require('fs'); -const config = require('../config').get(); - -module.exports = () => { - let cert; - try { - cert = fs.readFileSync(config.app.saml.certPath, 'utf-8'); - } catch (e) { - throw new Error('Could not read SAML certificate [saml.certPath]', e); - } - - return new SamlStrategy( - { - path: '/auth/saml/callback', - entryPoint: config.app.saml.ssoUrl, - cert - }, - (user, done) => done(null, user) - ); -}; diff --git a/backend/auth/SamlStrategy.ts b/backend/auth/SamlStrategy.ts new file mode 100644 index 0000000000..d330c48159 --- /dev/null +++ b/backend/auth/SamlStrategy.ts @@ -0,0 +1,23 @@ +// @ts-nocheck File not migrated fully to TS + +import { Strategy } from 'passport-saml'; +import fs from 'fs'; +import { getConfig } from '../config'; + +export default () => { + let cert; + try { + cert = fs.readFileSync(getConfig().app.saml.certPath, 'utf-8'); + } catch (e) { + throw new Error('Could not read SAML certificate [saml.certPath]', e); + } + + return new Strategy( + { + path: '/auth/saml/callback', + entryPoint: getConfig().app.saml.ssoUrl, + cert + }, + (user, done) => done(null, user) + ); +}; diff --git a/backend/auth/TokenStrategy.js b/backend/auth/TokenStrategy.ts similarity index 50% rename from backend/auth/TokenStrategy.js rename to backend/auth/TokenStrategy.ts index 90f6bb7412..5ac82273e3 100644 --- a/backend/auth/TokenStrategy.js +++ b/backend/auth/TokenStrategy.ts @@ -1,20 +1,17 @@ -/** - * Created by edenp on 7/30/17. - */ +// @ts-nocheck File not migrated fully to TS +import { Strategy } from 'passport-unique-token'; +import { getUser } from '../handler/AuthHandler'; +import { getLogger } from '../handler/LoggerHandler'; -const UniqueTokenStrategy = require('passport-unique-token').Strategy; -const AuthHandler = require('../handler/AuthHandler'); -const LoggerHandler = require('../handler/LoggerHandler'); +const logger = getLogger('Passport'); -const logger = LoggerHandler.getLogger('Passport'); - -module.exports = () => { - return new UniqueTokenStrategy( +export default () => { + return new Strategy( { tokenHeader: 'authentication-token' }, (token, done) => { - AuthHandler.getUser(token) + getUser(token) .then(user => { return done(null, user); }) diff --git a/backend/babel.config.js b/backend/babel.config.js new file mode 100644 index 0000000000..6cad0ca8fa --- /dev/null +++ b/backend/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'] +}; diff --git a/backend/config.js b/backend/config.js deleted file mode 100644 index f186b2c59e..0000000000 --- a/backend/config.js +++ /dev/null @@ -1,73 +0,0 @@ -/* eslint-disable - node/no-unpublished-require,node/no-missing-require,import/no-unresolved,global-require,import/no-dynamic-require */ -const _ = require('lodash'); -const flatten = require('flat'); - -const Utils = require('./utils'); - -const root = require('../conf/config.json'); -const app = require('../conf/app.json'); -const logging = require('../conf/logging.json'); -const dbOptions = require('../conf/db.options.json'); -const manager = require('../conf/manager.json'); -let userConfig = require('../conf/userConfig.json'); - -try { - const userDataConfigPath = Utils.getResourcePath('userConfig.json', true); - let userDataConfig = require(userDataConfigPath); - userDataConfig = _.pick(userDataConfig, _.keys(flatten(userConfig, { safe: true }))); // Security reason - get only allowed parameters - userConfig = _.defaultsDeep(userDataConfig, userConfig); // Create full user configuration -} catch (err) { - if (err.code !== 'MODULE_NOT_FOUND') { - throw err; - } -} - -let me = null; -try { - me = require('../conf/me.json'); -} catch (err) { - if (err.code !== 'MODULE_NOT_FOUND') { - throw err; - } -} - -module.exports = { - get(mode) { - const config = { - app: _.merge(app, root, logging, { db: { options: dbOptions } }, userConfig), - manager, - mode - }; - - _.merge(config, me); - - config.managerUrl = `${manager.protocol}://${manager.ip}:${manager.port}`; - - return config; - }, - - getForClient(mode) { - const config = this.get(mode); - - // For client only get from app config the relevant part (and not send passwords and shit) - return { - app: { - initialTemplate: config.app.initialTemplate, - maintenancePollingInterval: config.app.maintenancePollingInterval, - singleManager: config.app.singleManager, - whiteLabel: userConfig.whiteLabel, - saml: { - enabled: config.app.saml.enabled, - ssoUrl: config.app.saml.ssoUrl, - portalUrl: config.app.saml.portalUrl - }, - maps: userConfig.maps - }, - manager: { - ip: config.manager.ip - }, - mode: config.mode - }; - } -}; diff --git a/backend/config.ts b/backend/config.ts new file mode 100644 index 0000000000..21f0f5050c --- /dev/null +++ b/backend/config.ts @@ -0,0 +1,79 @@ +// @ts-nocheck File not migrated fully to TS +/* eslint-disable node/no-unpublished-import,global-require,import/no-dynamic-require */ +import _ from 'lodash'; +import flatten from 'flat'; + +import { getResourcePath } from './utils'; + +import root from '../conf/config.json'; +import app from '../conf/app.json'; +import logging from '../conf/logging.json'; +import dbOptions from '../conf/db.options.json'; +import manager from '../conf/manager.json'; + +// eslint-disable-next-line @typescript-eslint/no-var-requires +let userConfig = require('../conf/userConfig.json'); + +try { + const userDataConfigPath = getResourcePath('userConfig.json', true); + // eslint-disable-next-line @typescript-eslint/no-var-requires + let userDataConfig = require(userDataConfigPath); + userDataConfig = _.pick(userDataConfig, _.keys(flatten(userConfig, { safe: true }))); // Security reason - get only allowed parameters + userConfig = _.defaultsDeep(userDataConfig, userConfig); // Create full user configuration +} catch (err) { + if (err.code !== 'MODULE_NOT_FOUND') { + throw err; + } +} + +let me = null; + +export function loadMeJson() { + try { + // eslint-disable-next-line import/no-unresolved + me = require('../conf/me.json'); + } catch (err) { + if (err.code !== 'MODULE_NOT_FOUND') { + throw err; + } + } +} + +loadMeJson(); + +export function getConfig(mode?) { + const config = { + app: _.merge(app, root, logging, { db: { options: dbOptions } }, userConfig), + manager, + mode, + managerUrl: `${manager.protocol}://${manager.ip}:${manager.port}` + }; + + _.merge(config, me); + + return config; +} + +export function getClientConfig(mode) { + const config = getConfig(mode); + + // For client only get from app config the relevant part (and not send passwords and shit) + return { + app: { + initialTemplate: config.app.initialTemplate, + maintenancePollingInterval: config.app.maintenancePollingInterval, + singleManager: config.app.singleManager, + whiteLabel: userConfig.whiteLabel, + saml: { + enabled: config.app.saml.enabled, + ssoUrl: config.app.saml.ssoUrl, + portalUrl: config.app.saml.portalUrl + }, + maps: userConfig.maps + }, + manager: { + ip: config.manager.ip + }, + mode: config.mode + }; +} diff --git a/backend/consts.js b/backend/consts.js deleted file mode 100644 index 44e772d883..0000000000 --- a/backend/consts.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Created by jakubniezgoda on 07/11/2017. - */ -const _ = require('lodash'); - -module.exports = { - ALLOWED_METHODS_OBJECT: { get: 'GET', post: 'POST', put: 'PUT', delete: 'DELETE', patch: 'PATCH' }, - - CONTEXT_PATH: '/console', - - USER_DATA_PATH: '/userData', - APP_DATA_PATH: '/appData', - - WIDGET_ID_HEADER: 'widget-id', - TOKEN_COOKIE_NAME: 'XSRF-TOKEN', - ROLE_COOKIE_NAME: 'ROLE', - USERNAME_COOKIE_NAME: 'USERNAME', - - EDITION: { - PREMIUM: 'premium', - COMMUNITY: 'community' - }, - - SERVER_HOST: 'localhost', - SERVER_PORT: 8088, - - LAYOUT: { - TABS: 'tabs', - WIDGETS: 'widgets' - } -}; - -module.exports.ALLOWED_METHODS_ARRAY = _.values(module.exports.ALLOWED_METHODS_OBJECT); diff --git a/backend/consts.ts b/backend/consts.ts new file mode 100644 index 0000000000..dea00d98df --- /dev/null +++ b/backend/consts.ts @@ -0,0 +1,29 @@ +import _ from 'lodash'; + +const allowedMethods = { get: 'GET', post: 'POST', put: 'PUT', delete: 'DELETE', patch: 'PATCH' }; + +export const ALLOWED_METHODS_OBJECT = allowedMethods; +export const ALLOWED_METHODS_ARRAY = _.values(allowedMethods); + +export const CONTEXT_PATH = '/console'; + +export const USER_DATA_PATH = '/userData'; +export const APP_DATA_PATH = '/appData'; + +export const WIDGET_ID_HEADER = 'widget-id'; +export const TOKEN_COOKIE_NAME = 'XSRF-TOKEN'; +export const ROLE_COOKIE_NAME = 'ROLE'; +export const USERNAME_COOKIE_NAME = 'USERNAME'; + +export const EDITION = { + PREMIUM: 'premium', + COMMUNITY: 'community' +}; + +export const SERVER_HOST = 'localhost'; +export const SERVER_PORT = 8088; + +export const LAYOUT = { + TABS: 'tabs', + WIDGETS: 'widgets' +}; diff --git a/backend/db/Connection.js b/backend/db/Connection.js deleted file mode 100644 index c2407dc64a..0000000000 --- a/backend/db/Connection.js +++ /dev/null @@ -1,5 +0,0 @@ -const { DbInitializer } = require('cloudify-ui-common/backend'); -const dbConfig = require('../config').get().app.db; -const loggerFactory = require('../handler/LoggerHandler'); - -module.exports = new DbInitializer(dbConfig, loggerFactory, __dirname, ['Connection.js', 'types']); diff --git a/backend/db/Connection.ts b/backend/db/Connection.ts new file mode 100644 index 0000000000..62eafd33a4 --- /dev/null +++ b/backend/db/Connection.ts @@ -0,0 +1,13 @@ +// @ts-nocheck File not migrated fully to TS + +import path from 'path'; + +import { DbInitializer } from 'cloudify-ui-common/backend'; +import { getConfig } from '../config'; +import loggerFactory from '../handler/LoggerHandler'; + +const dbInitializer = new DbInitializer(getConfig().app.db, loggerFactory, path.resolve(__dirname, 'models')); + +export const { db } = dbInitializer; + +export default dbInitializer; diff --git a/backend/db/__mocks__/Connection.ts b/backend/db/__mocks__/Connection.ts new file mode 100644 index 0000000000..98e596d559 --- /dev/null +++ b/backend/db/__mocks__/Connection.ts @@ -0,0 +1,5 @@ +export const db = {}; + +export function mockDb(dbToSet: Record Promise>) { + Object.assign(db, dbToSet); +} diff --git a/backend/db/ApplicationModel.js b/backend/db/models/ApplicationModel.js similarity index 100% rename from backend/db/ApplicationModel.js rename to backend/db/models/ApplicationModel.js diff --git a/backend/db/BlueprintAdditionsModel.js b/backend/db/models/BlueprintAdditionsModel.js similarity index 100% rename from backend/db/BlueprintAdditionsModel.js rename to backend/db/models/BlueprintAdditionsModel.js diff --git a/backend/db/BlueprintUserDataModel.js b/backend/db/models/BlueprintUserDataModel.js similarity index 100% rename from backend/db/BlueprintUserDataModel.js rename to backend/db/models/BlueprintUserDataModel.js diff --git a/backend/db/ClientConfigModel.js b/backend/db/models/ClientConfigModel.js similarity index 100% rename from backend/db/ClientConfigModel.js rename to backend/db/models/ClientConfigModel.js diff --git a/backend/db/ResourcesModel.js b/backend/db/models/ResourcesModel.js similarity index 90% rename from backend/db/ResourcesModel.js rename to backend/db/models/ResourcesModel.js index 1a5198b9b0..db1c21ec1d 100644 --- a/backend/db/ResourcesModel.js +++ b/backend/db/models/ResourcesModel.js @@ -2,7 +2,7 @@ * Created by pposel on 10/04/2017. */ -const ResourceTypes = require('./types/ResourceTypes'); +const ResourceTypes = require('../types/ResourceTypes'); module.exports = (sequelize, DataTypes) => sequelize.define( diff --git a/backend/db/UserAppModel.js b/backend/db/models/UserAppModel.js similarity index 94% rename from backend/db/UserAppModel.js rename to backend/db/models/UserAppModel.js index 9e412e4388..41f64a45e6 100644 --- a/backend/db/UserAppModel.js +++ b/backend/db/models/UserAppModel.js @@ -1,7 +1,7 @@ /** * Created by kinneretzin on 13/02/2017. */ -const ServerSettings = require('../serverSettings'); +const ServerSettings = require('../../serverSettings'); module.exports = (sequelize, DataTypes) => { const UserApp = sequelize.define( diff --git a/backend/db/WidgetBackendModel.js b/backend/db/models/WidgetBackendModel.js similarity index 100% rename from backend/db/WidgetBackendModel.js rename to backend/db/models/WidgetBackendModel.js diff --git a/backend/handler/ArchiveHelper.js b/backend/handler/ArchiveHelper.js deleted file mode 100644 index 1d977d8db9..0000000000 --- a/backend/handler/ArchiveHelper.js +++ /dev/null @@ -1,207 +0,0 @@ -/** - * Created by pposel on 24/02/2017. - */ - -const _ = require('lodash'); -const fs = require('fs-extra'); -const pathlib = require('path'); -const sanitize = require('sanitize-filename'); -const decompress = require('decompress'); -const multer = require('multer'); -const ManagerHandler = require('./ManagerHandler'); -const RequestHandler = require('./RequestHandler'); - -const logger = require('./LoggerHandler').getLogger('ArchiveHelper'); - -module.exports = (() => { - function saveMultipartData(req, targetDir, multipartId) { - const storage = multer.diskStorage({ - destination(request, file, cb) { - logger.debug('Saving file on disk'); - - const archiveFolder = _.isFunction(targetDir) ? targetDir(file.originalname) : targetDir; - - fs.mkdirsSync(archiveFolder); - - request.archiveFolder = archiveFolder; - cb(null, archiveFolder); - }, - filename(request, file, cb) { - request.archiveFile = file.originalname; - cb(null, file.originalname); - } - }); - - const upload = multer({ storage }).single(multipartId); - - return new Promise((resolve, reject) => { - upload(req, null, err => { - if (err) { - reject(err); - } else { - logger.debug( - 'Archive saved from multipart data, archiveFolder:', - req.archiveFolder, - 'archiveFile:', - req.archiveFile - ); - resolve({ archiveFolder: req.archiveFolder, archiveFile: req.archiveFile }); - } - }); - }); - } - - function isExternalUrl(url) { - // eslint-disable-next-line security/detect-unsafe-regex - const ABSOLUTE_URL_REGEX = new RegExp('^(?:[a-z]+:)?//', 'i'); - - return ABSOLUTE_URL_REGEX.test(url); - } - - function extractFilename(contentDisposition) { - const regexp = /filename=([^;]*)/g; - const match = regexp.exec(contentDisposition); - if (!match) { - return ''; - } - - return match[1]; - } - - function saveDataFromUrl(url, targetDir, req) { - return new Promise((resolve, reject) => { - const HEADERS = { 'User-Agent': 'Node.js' }; - const archiveUrl = decodeURIComponent(url.trim()); - - logger.debug('Fetching file from url', archiveUrl); - - let getRequest = null; - const onErrorFetch = reject; - - const onSuccessFetch = response => { - let archiveFile = extractFilename(response.headers['content-disposition']); - - logger.debug('Filename extracted from content-disposition', archiveFile); - logger.debug('Content length', response.headers['content-length']); - - if (!archiveFile) { - const details = pathlib.parse(archiveUrl); - - const archiveExt = ['tar', 'bz2', 'gz', 'zip'].find(ext => _.includes(details.ext, ext)); - - if (archiveExt) { - archiveFile = details.base; - } else { - reject(`Unable to determine filename from url ${archiveUrl}`); - return; - } - - logger.debug('Filename build from url', archiveFile); - } - - // remove not allowed characters - archiveFile = sanitize(archiveFile); - - const archiveFolder = _.isFunction(targetDir) ? targetDir(archiveFile) : targetDir; - fs.mkdirsSync(archiveFolder); - const archivePath = pathlib.join(archiveFolder, archiveFile); - - logger.debug('Streaming to file', archivePath); - - response.pipe( - fs - .createWriteStream(archivePath) - .on('error', reject) - .on('close', () => { - logger.debug('archive saved, archivePath:', archivePath); - resolve({ archiveFolder, archiveFile, archivePath }); - }) - ); - }; - - if (isExternalUrl(archiveUrl)) { - const options = { options: { headers: HEADERS } }; - getRequest = RequestHandler.request('GET', archiveUrl, options, onSuccessFetch, onErrorFetch); - } else { - getRequest = ManagerHandler.request('GET', archiveUrl, HEADERS, null, onSuccessFetch, onErrorFetch); - } - - if (req) { - req.pipe(getRequest); - } - }); - } - - function storeSingleYamlFile(archivePath, archiveFile, targetDir) { - logger.debug('Storing single YAML file', pathlib.resolve(archivePath), targetDir); - - fs.mkdirsSync(targetDir); - fs.renameSync(archivePath, pathlib.join(targetDir, archiveFile)); - - return {}; - } - - function decompressArchive(archivePath, targetDir) { - logger.debug('Extracting archive', pathlib.resolve(archivePath), targetDir); - - fs.mkdirsSync(targetDir); - - return decompress(archivePath, targetDir); - } - - function removeOldExtracts(tempDir) { - const PAST_TIME = 60 * 60 * 1000; // remove archives older then 1 hour async - - fs.readdir(tempDir, (err, files) => { - if (err) { - logger.warn('Cannot remove old extracts. Error:', err); - return; - } - - files - .map(file => pathlib.join(tempDir, file)) - .filter(file => { - const now = new Date().getTime(); - const modTime = new Date(fs.statSync(file).mtime).getTime() + PAST_TIME; - - return now > modTime; - }) - .forEach(file => { - fs.removeSync(file); - }); - }); - - return Promise.resolve(); - } - - function cleanTempData(tempPath) { - logger.debug('Removing temporary data from', tempPath); - - return new Promise((resolve, reject) => { - fs.pathExists(tempPath).then(exists => { - if (exists) { - fs.remove(tempPath, err => { - if (err) { - const errorMessage = `Error removing temporary path ${tempPath}: ${err}`; - logger.error(errorMessage); - reject(errorMessage); - } else { - resolve(); - } - }); - } else { - resolve(); - } - }); - }); - } - - return { - saveMultipartData, - saveDataFromUrl, - decompressArchive, - storeSingleYamlFile, - removeOldExtracts, - cleanTempData - }; -})(); diff --git a/backend/handler/ArchiveHelper.ts b/backend/handler/ArchiveHelper.ts new file mode 100644 index 0000000000..d1b0c85f98 --- /dev/null +++ b/backend/handler/ArchiveHelper.ts @@ -0,0 +1,194 @@ +// @ts-nocheck File not migrated fully to TS +import _ from 'lodash'; +import fs from 'fs-extra'; +import pathlib from 'path'; +import sanitize from 'sanitize-filename'; +import decompress from 'decompress'; +import multer from 'multer'; +import * as ManagerHandler from './ManagerHandler'; +import * as RequestHandler from './RequestHandler'; +import { getLogger } from './LoggerHandler'; + +const logger = getLogger('ArchiveHelper'); + +export function saveMultipartData(req, targetDir, multipartId) { + const storage = multer.diskStorage({ + destination(request, file, cb) { + logger.debug('Saving file on disk'); + + const archiveFolder = _.isFunction(targetDir) ? targetDir(file.originalname) : targetDir; + + fs.mkdirsSync(archiveFolder); + + request.archiveFolder = archiveFolder; + cb(null, archiveFolder); + }, + filename(request, file, cb) { + request.archiveFile = file.originalname; + cb(null, file.originalname); + } + }); + + const upload = multer({ storage }).single(multipartId); + + return new Promise((resolve, reject) => { + upload(req, null, err => { + if (err) { + reject(err); + } else { + logger.debug( + 'Archive saved from multipart data, archiveFolder:', + req.archiveFolder, + 'archiveFile:', + req.archiveFile + ); + resolve({ archiveFolder: req.archiveFolder, archiveFile: req.archiveFile }); + } + }); + }); +} + +function isExternalUrl(url) { + // eslint-disable-next-line security/detect-unsafe-regex + const ABSOLUTE_URL_REGEX = new RegExp('^(?:[a-z]+:)?//', 'i'); + + return ABSOLUTE_URL_REGEX.test(url); +} + +function extractFilename(contentDisposition) { + const regexp = /filename=([^;]*)/g; + const match = regexp.exec(contentDisposition); + if (!match) { + return ''; + } + + return match[1]; +} + +export function saveDataFromUrl(url, targetDir, req) { + return new Promise((resolve, reject) => { + const HEADERS = { 'User-Agent': 'Node.js' }; + const archiveUrl = decodeURIComponent(url.trim()); + + logger.debug('Fetching file from url', archiveUrl); + + let getRequest = null; + const onErrorFetch = reject; + + const onSuccessFetch = response => { + let archiveFile = extractFilename(response.headers['content-disposition']); + + logger.debug('Filename extracted from content-disposition', archiveFile); + logger.debug('Content length', response.headers['content-length']); + + if (!archiveFile) { + const details = pathlib.parse(archiveUrl); + + const archiveExt = ['tar', 'bz2', 'gz', 'zip'].find(ext => _.includes(details.ext, ext)); + + if (archiveExt) { + archiveFile = details.base; + } else { + reject(`Unable to determine filename from url ${archiveUrl}`); + return; + } + + logger.debug('Filename build from url', archiveFile); + } + + // remove not allowed characters + archiveFile = sanitize(archiveFile); + + const archiveFolder = _.isFunction(targetDir) ? targetDir(archiveFile) : targetDir; + fs.mkdirsSync(archiveFolder); + const archivePath = pathlib.join(archiveFolder, archiveFile); + + logger.debug('Streaming to file', archivePath); + + response.pipe( + fs + .createWriteStream(archivePath) + .on('error', reject) + .on('close', () => { + logger.debug('archive saved, archivePath:', archivePath); + resolve({ archiveFolder, archiveFile, archivePath }); + }) + ); + }; + + if (isExternalUrl(archiveUrl)) { + const options = { options: { headers: HEADERS } }; + getRequest = RequestHandler.request('GET', archiveUrl, options, onSuccessFetch, onErrorFetch); + } else { + getRequest = ManagerHandler.request('GET', archiveUrl, HEADERS, null, onSuccessFetch, onErrorFetch); + } + + if (req) { + req.pipe(getRequest); + } + }); +} + +export function storeSingleYamlFile(archivePath, archiveFile, targetDir) { + logger.debug('Storing single YAML file', pathlib.resolve(archivePath), targetDir); + + fs.mkdirsSync(targetDir); + fs.renameSync(archivePath, pathlib.join(targetDir, archiveFile)); + + return {}; +} + +export function decompressArchive(archivePath, targetDir) { + logger.debug('Extracting archive', pathlib.resolve(archivePath), targetDir); + + fs.mkdirsSync(targetDir); + + return decompress(archivePath, targetDir); +} + +export function removeOldExtracts(tempDir) { + const PAST_TIME = 60 * 60 * 1000; // remove archives older then 1 hour async + + fs.readdir(tempDir, (err, files) => { + if (err) { + logger.warn('Cannot remove old extracts. Error:', err); + return; + } + + files + .map(file => pathlib.join(tempDir, file)) + .filter(file => { + const now = new Date().getTime(); + const modTime = new Date(fs.statSync(file).mtime).getTime() + PAST_TIME; + + return now > modTime; + }) + .forEach(file => { + fs.removeSync(file); + }); + }); + + return Promise.resolve(); +} + +export function cleanTempData(tempPath) { + logger.debug('Removing temporary data from', tempPath); + + return new Promise((resolve, reject) => { + fs.pathExists(tempPath).then(exists => { + if (exists) { + fs.remove(tempPath, err => { + if (err) { + const errorMessage = `Error removing temporary path ${tempPath}: ${err}`; + logger.error(errorMessage); + reject(errorMessage); + } else { + resolve(); + } + }); + } else { + resolve(); + } + }); + }); +} diff --git a/backend/handler/AuthHandler.js b/backend/handler/AuthHandler.js deleted file mode 100644 index 9d24317775..0000000000 --- a/backend/handler/AuthHandler.js +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Created by edenp on 7/30/17. - */ - -const _ = require('lodash'); -const Consts = require('../consts'); -const ManagerHandler = require('./ManagerHandler'); - -const ServerSettings = require('../serverSettings'); -const logger = require('./LoggerHandler').getLogger('AuthHandler'); - -let authorizationCache = {}; - -class AuthHandler { - static getToken(basicAuth) { - return ManagerHandler.jsonRequest('GET', '/tokens', { - Authorization: basicAuth - }); - } - - static getTenants(token) { - return ManagerHandler.jsonRequest('GET', '/tenants?_get_all_results=true&_include=name', { - 'Authentication-Token': token - }); - } - - static getUser(token) { - return ManagerHandler.jsonRequest('GET', '/user?_get_data=true', { - 'Authentication-Token': token - }); - } - - static isProductLicensed(version) { - return !_.isEqual(version.edition, Consts.EDITION.COMMUNITY); - } - - static getLicense(token) { - return ManagerHandler.jsonRequest('GET', '/license', { - 'Authentication-Token': token - }); - } - - static getTokenViaSamlResponse(samlResponse) { - return ManagerHandler.jsonRequest('POST', '/tokens', null, { - 'saml-response': samlResponse - }); - } - - static getAndCacheConfig(token) { - return ManagerHandler.jsonRequest('GET', '/config', { - 'Authentication-Token': token - }).then(config => { - authorizationCache = config.authorization; - logger.debug('Authorization config cached successfully.'); - return Promise.resolve(authorizationCache); - }); - } - - static isRbacInCache() { - return !_.isEmpty(authorizationCache); - } - - static async getRBAC(token) { - if (!AuthHandler.isRbacInCache()) { - logger.debug('No RBAC data in cache.'); - await AuthHandler.getAndCacheConfig(token); - } else { - logger.debug('RBAC data found in cache.'); - } - return authorizationCache; - } - - static getManagerVersion(token) { - return ManagerHandler.jsonRequest('GET', '/version', { 'Authentication-Token': token }).then(version => { - // set community mode from manager API only if mode is not set from the command line - if ( - ServerSettings.settings.mode === ServerSettings.MODE_MAIN && - version.edition === ServerSettings.MODE_COMMUNITY - ) { - ServerSettings.settings.mode = ServerSettings.MODE_COMMUNITY; - } - - return Promise.resolve(version); - }); - } - - static isAuthorized(user, authorizedRoles) { - const systemRole = user.role; - const groupSystemRoles = _.keys(user.group_system_roles); - - const userSystemRoles = _.uniq(_.concat(systemRole, groupSystemRoles)); - return _.intersection(userSystemRoles, authorizedRoles).length > 0; - } -} - -module.exports = AuthHandler; diff --git a/backend/handler/AuthHandler.ts b/backend/handler/AuthHandler.ts new file mode 100644 index 0000000000..009ca30774 --- /dev/null +++ b/backend/handler/AuthHandler.ts @@ -0,0 +1,88 @@ +// @ts-nocheck File not migrated fully to TS +import _ from 'lodash'; +import { EDITION } from '../consts'; +import { jsonRequest } from './ManagerHandler'; + +import { getMode, setMode, MODE_MAIN, MODE_COMMUNITY } from '../serverSettings'; +import { getLogger } from './LoggerHandler'; + +const logger = getLogger('AuthHandler'); + +let authorizationCache = {}; + +export function getToken(basicAuth) { + return jsonRequest('GET', '/tokens', { + Authorization: basicAuth + }); +} + +export function getTenants(token) { + return jsonRequest('GET', '/tenants?_get_all_results=true&_include=name', { + 'Authentication-Token': token + }); +} + +export function getUser(token) { + return jsonRequest('GET', '/user?_get_data=true', { + 'Authentication-Token': token + }); +} + +export function isProductLicensed(version) { + return !_.isEqual(version.edition, EDITION.COMMUNITY); +} + +export function getLicense(token) { + return jsonRequest('GET', '/license', { + 'Authentication-Token': token + }); +} + +export function getTokenViaSamlResponse(samlResponse) { + return jsonRequest('POST', '/tokens', null, { + 'saml-response': samlResponse + }); +} + +export function getAndCacheConfig(token) { + return jsonRequest('GET', '/config', { + 'Authentication-Token': token + }).then(config => { + authorizationCache = config.authorization; + logger.debug('Authorization config cached successfully.'); + return Promise.resolve(authorizationCache); + }); +} + +export function isRbacInCache() { + return !_.isEmpty(authorizationCache); +} + +export async function getRBAC(token) { + if (!isRbacInCache()) { + logger.debug('No RBAC data in cache.'); + await getAndCacheConfig(token); + } else { + logger.debug('RBAC data found in cache.'); + } + return authorizationCache; +} + +export function getManagerVersion(token) { + return jsonRequest('GET', '/version', { 'Authentication-Token': token }).then(version => { + // set community mode from manager API only if mode is not set from the command line + if (getMode() === MODE_MAIN && version.edition === MODE_COMMUNITY) { + setMode(MODE_COMMUNITY); + } + + return Promise.resolve(version); + }); +} + +export function isAuthorized(user, authorizedRoles) { + const systemRole = user.role; + const groupSystemRoles = _.keys(user.group_system_roles); + + const userSystemRoles = _.uniq(_.concat(systemRole, groupSystemRoles)); + return _.intersection(userSystemRoles, authorizedRoles).length > 0; +} diff --git a/backend/handler/BackendHandler.js b/backend/handler/BackendHandler.js deleted file mode 100644 index c033bc224b..0000000000 --- a/backend/handler/BackendHandler.js +++ /dev/null @@ -1,219 +0,0 @@ -/** - * Created by pposel on 13/09/2017. - */ - -const fs = require('fs-extra'); -const pathlib = require('path'); -const _ = require('lodash'); -const { NodeVM, VMScript } = require('vm2'); - -const config = require('../config').get(); -const consts = require('../consts'); -const { db } = require('../db/Connection'); -const Utils = require('../utils'); -const helper = require('./services'); - -const logger = require('./LoggerHandler').getLogger('WidgetBackend'); - -const builtInWidgetsFolder = Utils.getResourcePath('widgets', false); -const userWidgetsFolder = Utils.getResourcePath('widgets', true); -const getServiceString = (widgetId, method, serviceName) => `widget=${widgetId} method=${method} name=${serviceName}`; - -/* eslint-disable no-param-reassign */ -const BackendRegistrator = (widgetId, resolve, reject) => ({ - register: (serviceName, method, service) => { - if (!serviceName) { - return reject('Service name must be provided'); - } - serviceName = _.toUpper(serviceName); - - if (!_.isString(method)) { - service = method; - method = consts.ALLOWED_METHODS_OBJECT.get; - } else if (!_.isNil(method)) { - method = _.toUpper(method); - if (!_.includes(consts.ALLOWED_METHODS_ARRAY, method)) { - return reject( - `Method '${method}' not allowed. Valid methods: ${consts.ALLOWED_METHODS_ARRAY.toString()}` - ); - } - } - - if (!service) { - return reject('Service body must be provided'); - } - if (!_.isFunction(service)) { - return reject('Service body must be a function (function(request, response, next, helper) {...})'); - } - - logger.info(`--- registering service ${getServiceString(widgetId, method, serviceName)}`); - - return db.WidgetBackend.findOrCreate({ - where: { - widgetId, - serviceName, - method - }, - defaults: { - script: '' - } - }) - .spread(widgetBackend => { - logger.debug(`--- updating entry for service: ${getServiceString(widgetId, method, serviceName)}`); - return widgetBackend.update( - { script: new VMScript(`module.exports = ${service.toString()}`) }, - { fields: ['script'] } - ); - }) - .then(() => { - logger.info(`--- registered service: ${getServiceString(widgetId, method, serviceName)}`); - return resolve(); - }) - .catch(error => { - logger.error(error); - return reject(`--- error registering service: ${getServiceString(widgetId, method, serviceName)}`); - }); - } -}); -/* eslint-enable no-param-reassign */ - -module.exports = (() => { - function getUserWidgets() { - return fs - .readdirSync(userWidgetsFolder) - .filter( - dir => - fs.lstatSync(pathlib.resolve(userWidgetsFolder, dir)).isDirectory() && - _.indexOf(config.app.widgets.ignoreFolders, dir) < 0 - ); - } - - function getBuiltInWidgets() { - return fs - .readdirSync(builtInWidgetsFolder) - .filter( - dir => - fs.lstatSync(pathlib.resolve(builtInWidgetsFolder, dir)).isDirectory() && - _.indexOf(config.app.widgets.ignoreFolders, dir) < 0 - ); - } - - function importWidgetBackend(widgetId, isCustom = true) { - let widgetsFolder = userWidgetsFolder; - if (!isCustom) { - widgetsFolder = builtInWidgetsFolder; - } - const backendFile = pathlib.resolve(widgetsFolder, widgetId, config.app.widgets.backendFilename); - - if (fs.existsSync(backendFile)) { - logger.info(`-- initializing file ${backendFile}`); - - try { - const vm = new NodeVM({ - sandbox: { - _, - backendFile, - widgetId, - BackendRegistrator - }, - require: { - context: 'sandbox', - external: config.app.widgets.allowedModules - } - }); - - const script = `module.exports = new Promise((resolve, reject) => { - try { - let backend = require(backendFile); - if (_.isFunction(backend)) { - backend(BackendRegistrator(widgetId, resolve, reject)); - } else { - reject('Backend definition must be a function (module.exports = function(BackendRegistrator) {...})'); - } - } catch (error) { - reject(error); - } - });`; - - return vm.run(script, pathlib.resolve(`${process.cwd()}/${widgetId}`)); - } catch (err) { - logger.info('reject', backendFile); - return Promise.reject( - `Error during importing widget backend from file ${backendFile} - ${err.message}` - ); - } - } else { - return Promise.resolve(); - } - } - - function initWidgetBackends() { - logger.info('Scanning widget backend files...'); - - const userWidgets = getUserWidgets(); - const builtInWidgets = getBuiltInWidgets(); - - const promises = []; - _.each(userWidgets, widgetId => - promises.push(new Promise((resolve, reject) => importWidgetBackend(widgetId).then(resolve).catch(reject))) - ); - _.each(builtInWidgets, widgetId => - promises.push( - new Promise((resolve, reject) => importWidgetBackend(widgetId, false).then(resolve).catch(reject)) - ) - ); - - return Promise.all(promises) - .then(() => { - logger.info('Widget backend files for registration completed'); - return Promise.resolve(); - }) - .catch(error => { - logger.error('Widget backend files registration cannot be completed'); - return Promise.reject(error); - }); - } - - /* eslint-disable no-param-reassign */ - function callService(serviceName, method, req, res, next) { - const widgetId = req.header(consts.WIDGET_ID_HEADER); - method = _.toUpper(method); - serviceName = _.toUpper(serviceName); - - return db.WidgetBackend.findOne({ where: { widgetId, serviceName, method } }) - .catch(() => { - return Promise.reject( - `There is no service ${serviceName} for method ${method} for widget ${widgetId} registered` - ); - }) - .then(widgetBackend => { - const script = _.get(widgetBackend, 'script.code', null); - - if (script) { - const vm = new NodeVM({ - require: { - external: config.app.widgets.allowedModules - } - }); - return vm.run(script, pathlib.resolve(`${process.cwd()}/${widgetId}`))(req, res, next, helper); - } - return Promise.reject( - `No script for service ${serviceName} for method ${method} for widget ${widgetId}` - ); - }); - } - /* eslint-enable no-param-reassign */ - - function removeWidgetBackend(widgetId) { - return db.WidgetBackend.destroy({ where: { widgetId } }).then(() => - logger.debug(`Deleted widget backend for ${widgetId}.`) - ); - } - - return { - importWidgetBackend, - initWidgetBackends, - removeWidgetBackend, - callService - }; -})(); diff --git a/backend/handler/BackendHandler.ts b/backend/handler/BackendHandler.ts new file mode 100644 index 0000000000..3651ca1e4c --- /dev/null +++ b/backend/handler/BackendHandler.ts @@ -0,0 +1,203 @@ +// @ts-nocheck File not migrated fully to TS +import fs from 'fs-extra'; +import pathlib from 'path'; +import _ from 'lodash'; +import { NodeVM, VMScript } from 'vm2'; + +import { getConfig } from '../config'; +import { ALLOWED_METHODS_OBJECT, ALLOWED_METHODS_ARRAY, WIDGET_ID_HEADER } from '../consts'; +import { db } from '../db/Connection'; +import { getResourcePath } from '../utils'; +import * as helper from './services'; + +import { getLogger } from './LoggerHandler'; + +const logger = getLogger('WidgetBackend'); + +const builtInWidgetsFolder = getResourcePath('widgets', false); +const userWidgetsFolder = getResourcePath('widgets', true); +const getServiceString = (widgetId, method, serviceName) => `widget=${widgetId} method=${method} name=${serviceName}`; + +/* eslint-disable no-param-reassign */ +const BackendRegistrator = (widgetId, resolve, reject) => ({ + register: (serviceName, method, service) => { + if (!serviceName) { + return reject('Service name must be provided'); + } + serviceName = _.toUpper(serviceName); + + if (!_.isString(method)) { + service = method; + method = ALLOWED_METHODS_OBJECT.get; + } else if (!_.isNil(method)) { + method = _.toUpper(method); + if (!_.includes(ALLOWED_METHODS_ARRAY, method)) { + return reject(`Method '${method}' not allowed. Valid methods: ${ALLOWED_METHODS_ARRAY.toString()}`); + } + } + + if (!service) { + return reject('Service body must be provided'); + } + if (!_.isFunction(service)) { + return reject('Service body must be a function (function(request, response, next, helper) {...})'); + } + + logger.info(`--- registering service ${getServiceString(widgetId, method, serviceName)}`); + + return db.WidgetBackend.findOrCreate({ + where: { + widgetId, + serviceName, + method + }, + defaults: { + script: '' + } + }) + .spread(widgetBackend => { + logger.debug(`--- updating entry for service: ${getServiceString(widgetId, method, serviceName)}`); + return widgetBackend.update( + { script: new VMScript(`module.exports = ${service.toString()}`) }, + { fields: ['script'] } + ); + }) + .then(() => { + logger.info(`--- registered service: ${getServiceString(widgetId, method, serviceName)}`); + return resolve(); + }) + .catch(error => { + logger.error(error); + return reject(`--- error registering service: ${getServiceString(widgetId, method, serviceName)}`); + }); + } +}); +/* eslint-enable no-param-reassign */ + +function getUserWidgets() { + return fs + .readdirSync(userWidgetsFolder) + .filter( + dir => + fs.lstatSync(pathlib.resolve(userWidgetsFolder, dir)).isDirectory() && + _.indexOf(getConfig().app.widgets.ignoreFolders, dir) < 0 + ); +} + +function getBuiltInWidgets() { + return fs + .readdirSync(builtInWidgetsFolder) + .filter( + dir => + fs.lstatSync(pathlib.resolve(builtInWidgetsFolder, dir)).isDirectory() && + _.indexOf(getConfig().app.widgets.ignoreFolders, dir) < 0 + ); +} + +export function importWidgetBackend(widgetId, isCustom = true) { + let widgetsFolder = userWidgetsFolder; + if (!isCustom) { + widgetsFolder = builtInWidgetsFolder; + } + const backendFile = pathlib.resolve(widgetsFolder, widgetId, getConfig().app.widgets.backendFilename); + + if (fs.existsSync(backendFile)) { + logger.info(`-- initializing file ${backendFile}`); + + try { + const vm = new NodeVM({ + sandbox: { + _, + backendFile, + widgetId, + BackendRegistrator + }, + require: { + context: 'sandbox', + external: getConfig().app.widgets.allowedModules + } + }); + + const script = `module.exports = new Promise((resolve, reject) => { + try { + let backend = require(backendFile); + if (_.isFunction(backend)) { + backend(BackendRegistrator(widgetId, resolve, reject)); + } else { + reject('Backend definition must be a function (module.exports = function(BackendRegistrator) {...})'); + } + } catch (error) { + reject(error); + } + });`; + + return vm.run(script, pathlib.resolve(`${process.cwd()}/${widgetId}`)); + } catch (err) { + logger.info('reject', backendFile); + return Promise.reject(`Error during importing widget backend from file ${backendFile} - ${err.message}`); + } + } else { + return Promise.resolve(); + } +} + +export function initWidgetBackends() { + logger.info('Scanning widget backend files...'); + + const userWidgets = getUserWidgets(); + const builtInWidgets = getBuiltInWidgets(); + + const promises = []; + _.each(userWidgets, widgetId => + promises.push(new Promise((resolve, reject) => importWidgetBackend(widgetId).then(resolve).catch(reject))) + ); + _.each(builtInWidgets, widgetId => + promises.push( + new Promise((resolve, reject) => importWidgetBackend(widgetId, false).then(resolve).catch(reject)) + ) + ); + + return Promise.all(promises) + .then(() => { + logger.info('Widget backend files for registration completed'); + return Promise.resolve(); + }) + .catch(error => { + logger.error('Widget backend files registration cannot be completed'); + return Promise.reject(error); + }); +} + +/* eslint-disable no-param-reassign */ +export function callService(serviceName, method, req, res, next) { + const widgetId = req.header(WIDGET_ID_HEADER); + method = _.toUpper(method); + serviceName = _.toUpper(serviceName); + + return db.WidgetBackend.findOne({ where: { widgetId, serviceName, method } }) + .catch(() => { + return Promise.reject( + `There is no service ${serviceName} for method ${method} for widget ${widgetId} registered` + ); + }) + .then(widgetBackend => { + const script = _.get(widgetBackend, 'script.code', null); + + if (script) { + const vm = new NodeVM({ + require: { + external: getConfig().app.widgets.allowedModules + } + }); + return vm.run(script, pathlib.resolve(`${process.cwd()}/${widgetId}`))(req, res, next, helper); + } + return Promise.reject(`No script for service ${serviceName} for method ${method} for widget ${widgetId}`); + }); +} +/* eslint-enable no-param-reassign */ + +export function removeWidgetBackend(widgetId) { + return db.WidgetBackend.destroy({ where: { widgetId } }).then(() => + logger.debug(`Deleted widget backend for ${widgetId}.`) + ); +} diff --git a/backend/handler/FilterHandler.js b/backend/handler/FilterHandler.ts similarity index 76% rename from backend/handler/FilterHandler.js rename to backend/handler/FilterHandler.ts index d9cfb75b0b..5388cea598 100644 --- a/backend/handler/FilterHandler.js +++ b/backend/handler/FilterHandler.ts @@ -1,9 +1,13 @@ -const _ = require('lodash'); -const { db } = require('../db/Connection'); -const { LAYOUT } = require('../consts'); -const logger = require('./LoggerHandler').getLogger('FilterHandler'); +// @ts-nocheck File not migrated fully to TS +import _ from 'lodash'; +import { db } from '../db/Connection'; +import { LAYOUT } from '../consts'; +import { getLogger } from './LoggerHandler'; -async function getFilterUsage(filterId) { +const logger = getLogger('FilterHandler'); + +// eslint-disable-next-line import/prefer-default-export +export async function getFilterUsage(filterId) { const userAppsArr = await db.UserApp.findAll({ attributes: ['appData', 'username'] }); const filterUses = []; @@ -33,7 +37,3 @@ async function getFilterUsage(filterId) { return filterUses; } - -module.exports = { - getFilterUsage -}; diff --git a/backend/handler/LoggerHandler.js b/backend/handler/LoggerHandler.js deleted file mode 100644 index fa0c495c9f..0000000000 --- a/backend/handler/LoggerHandler.js +++ /dev/null @@ -1,14 +0,0 @@ -const { initLogging } = require('cloudify-ui-common/backend'); -const _ = require('lodash'); -const config = require('../config').get().app; - -const loggerFactory = initLogging(config); - -loggerFactory.getStream = category => { - const logger = loggerFactory.getLogger(category); - return { - write: message => logger.info(_.trim(message)) - }; -}; - -module.exports = loggerFactory; diff --git a/backend/handler/LoggerHandler.ts b/backend/handler/LoggerHandler.ts new file mode 100644 index 0000000000..7d4aa74e2b --- /dev/null +++ b/backend/handler/LoggerHandler.ts @@ -0,0 +1,17 @@ +// @ts-nocheck File not migrated fully to TS +import { initLogging } from 'cloudify-ui-common/backend'; +import _ from 'lodash'; +import { getConfig } from '../config'; + +const loggerFactory = initLogging(getConfig().app); + +loggerFactory.getStream = category => { + const logger = loggerFactory.getLogger(category); + return { + write: message => logger.info(_.trim(message)) + }; +}; + +export default loggerFactory; + +export const { getLogger } = loggerFactory; diff --git a/backend/handler/ManagerHandler.js b/backend/handler/ManagerHandler.js deleted file mode 100644 index 324c6ef72c..0000000000 --- a/backend/handler/ManagerHandler.js +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Created by jakubniezgoda on 18/07/2017. - */ - -const _ = require('lodash'); -const fs = require('fs-extra'); -const config = require('../config').get(); -const logger = require('./LoggerHandler').getLogger('ManagerHandler'); -const RequestHandler = require('./RequestHandler'); - -module.exports = (() => { - let caFile = null; - try { - caFile = _.get(config, 'app.ssl.ca') ? fs.readFileSync(config.app.ssl.ca) : null; - } catch (e) { - throw new Error('Could not setup ssl ca, error loading file.', e); - } - - function getManagerUrl() { - return config.managerUrl; - } - - function getApiUrl() { - return `${config.managerUrl}/api/${config.manager.apiVersion}`; - } - - function updateOptions(options, method, timeout, headers, data) { - if (caFile) { - logger.debug('Adding CA file to Agent Options'); - options.agentOptions = { - ca: caFile - }; - } - - options.timeout = timeout || config.app.proxy.timeouts[method.toLowerCase()]; - - if (headers) { - options.headers = _.omit(headers, 'host'); - } - - if (data) { - options.json = data; - try { - const strData = JSON.stringify(data); - options.headers = { ...options.headers, 'content-length': Buffer.byteLength(strData) }; - } catch (error) { - logger.error('Invalid payload data. Error:', error); - } - } - } - - function request(method, url, headers, data, onSuccess, onError, timeout) { - const requestUrl = this.getApiUrl() + (_.startsWith(url, '/') ? url : `/${url}`); - const requestOptions = {}; - this.updateOptions(requestOptions, method, timeout, headers, data); - - logger.debug(`Preparing ${method} request to manager: ${requestUrl}`); - return RequestHandler.request(method, requestUrl, requestOptions, onSuccess, onError); - } - - // the request assumes the response is JSON - function jsonRequest(method, url, headers, data, timeout) { - return new Promise((resolve, reject) => { - this.request( - method, - url, - headers, - data, - res => { - const isSuccess = res.statusCode >= 200 && res.statusCode < 300; - - RequestHandler.getResponseJson(res) - .then(json => (isSuccess ? resolve(json) : reject(json))) - .catch(e => - isSuccess - ? reject(`response data could not be parsed to JSON: ${e}`) - : reject(res.statusMessage) - ); - }, - err => reject(err), - timeout - ); - }); - } - - return { - getApiUrl, - getManagerUrl, - updateOptions, - request, - jsonRequest - }; -})(); diff --git a/backend/handler/ManagerHandler.ts b/backend/handler/ManagerHandler.ts new file mode 100644 index 0000000000..5ecb6ee0bb --- /dev/null +++ b/backend/handler/ManagerHandler.ts @@ -0,0 +1,84 @@ +// @ts-nocheck File not migrated fully to TS + +import _ from 'lodash'; +import fs from 'fs-extra'; +import { getLogger } from './LoggerHandler'; +import { getConfig } from '../config'; +import * as RequestHandler from './RequestHandler'; + +const logger = getLogger('ManagerHandler'); + +let caFile = null; +try { + const caPath = _.get(getConfig(), 'app.ssl.ca'); + caFile = caPath ? fs.readFileSync(caPath) : null; +} catch (e) { + throw new Error('Could not setup ssl ca, error loading file.', e); +} + +export function getManagerUrl() { + return getConfig().managerUrl; +} + +export function getApiUrl() { + return `${getConfig().managerUrl}/api/${getConfig().manager.apiVersion}`; +} + +export function updateOptions(options, method, timeout, headers, data) { + if (caFile) { + logger.debug('Adding CA file to Agent Options'); + options.agentOptions = { + ca: caFile + }; + } + + options.timeout = timeout || getConfig().app.proxy.timeouts[method.toLowerCase()]; + + if (headers) { + options.headers = _.omit(headers, 'host'); + } + + if (data) { + options.json = data; + try { + const strData = JSON.stringify(data); + options.headers = { ...options.headers, 'content-length': Buffer.byteLength(strData) }; + } catch (error) { + logger.error('Invalid payload data. Error:', error); + } + } +} + +export function request(method, url, headers, data, onSuccess, onError, timeout) { + const requestUrl = getApiUrl() + (_.startsWith(url, '/') ? url : `/${url}`); + const requestOptions = {}; + updateOptions(requestOptions, method, timeout, headers, data); + + logger.debug(`Preparing ${method} request to manager: ${requestUrl}`); + return RequestHandler.request(method, requestUrl, requestOptions, onSuccess, onError); +} + +// the request assumes the response is JSON +export function jsonRequest(method, url, headers, data, timeout) { + return new Promise((resolve, reject) => { + request( + method, + url, + headers, + data, + res => { + const isSuccess = res.statusCode >= 200 && res.statusCode < 300; + + RequestHandler.getResponseJson(res) + .then(json => (isSuccess ? resolve(json) : reject(json))) + .catch(e => + isSuccess + ? reject(`response data could not be parsed to JSON: ${e}`) + : reject(res.statusMessage) + ); + }, + err => reject(err), + timeout + ); + }); +} diff --git a/backend/handler/RequestHandler.js b/backend/handler/RequestHandler.js deleted file mode 100644 index dc2029c1a9..0000000000 --- a/backend/handler/RequestHandler.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Created by jakubniezgoda on 11/08/2017. - */ - -const req = require('request'); -const logger = require('./LoggerHandler').getLogger('RequestHandler'); - -module.exports = (() => { - function request(method, requestUrl, options, onSuccess, onError) { - options.method = method; - - logger.debug(`Calling ${options.method} request to: ${requestUrl}`); - return req(requestUrl, options).on('error', onError).on('response', onSuccess); - } - - function getResponseJson(res) { - return new Promise((resolve, reject) => { - let body = ''; - res.on('data', chunk => { - body += chunk; - }); - res.on('end', () => { - try { - const jsonResponse = JSON.parse(body); - resolve(jsonResponse); - } catch (e) { - reject(e); - } - }); - }); - } - - return { - request, - getResponseJson - }; -})(); diff --git a/backend/handler/RequestHandler.ts b/backend/handler/RequestHandler.ts new file mode 100644 index 0000000000..232f400333 --- /dev/null +++ b/backend/handler/RequestHandler.ts @@ -0,0 +1,30 @@ +// @ts-nocheck File not migrated fully to TS + +import req from 'request'; +import { getLogger } from './LoggerHandler'; + +const logger = getLogger('RequestHandler'); + +export function request(method, requestUrl, options, onSuccess, onError) { + options.method = method; + + logger.debug(`Calling ${options.method} request to: ${requestUrl}`); + return req(requestUrl, options).on('error', onError).on('response', onSuccess); +} + +export function getResponseJson(res) { + return new Promise((resolve, reject) => { + let body = ''; + res.on('data', chunk => { + body += chunk; + }); + res.on('end', () => { + try { + const jsonResponse = JSON.parse(body); + resolve(jsonResponse); + } catch (e) { + reject(e); + } + }); + }); +} diff --git a/backend/handler/SourceHandler.js b/backend/handler/SourceHandler.js deleted file mode 100644 index 706391a125..0000000000 --- a/backend/handler/SourceHandler.js +++ /dev/null @@ -1,283 +0,0 @@ -/** - * Created by pposel on 24/02/2017. - */ - -const _ = require('lodash'); -const os = require('os'); -const fs = require('fs-extra'); -const pathlib = require('path'); -const url = require('url'); -const yaml = require('js-yaml'); - -const config = require('../config').get(); -const ArchiveHelper = require('./ArchiveHelper'); -const Utils = require('../utils'); - -const logger = require('./LoggerHandler').getLogger('SourceHandler'); - -const browseSourcesDir = pathlib.join(os.tmpdir(), config.app.source.browseSourcesDir); -const lookupYamlsDir = pathlib.join(os.tmpdir(), config.app.source.lookupYamlsDir); - -const blueprintExtractDir = 'extracted'; - -module.exports = (() => { - function isUnixHiddenPath(path) { - // eslint-disable-next-line no-useless-escape - return /(^|.\/)\.+[^\/\.]/g.test(path); - } - - function toRelativeUrl(relativePath) { - const absoluteUrl = url.pathToFileURL(relativePath); - const relativeUrl = absoluteUrl.pathname.substring(url.pathToFileURL('').pathname.length + 1); - return relativeUrl; - } - - function scanRecursive(rootDir, scannedFileOrDirPath) { - const stats = fs.statSync(scannedFileOrDirPath); - const name = pathlib.basename(scannedFileOrDirPath); - - if (stats.isSymbolicLink() || isUnixHiddenPath(name)) { - return null; - } - - const item = { - key: toRelativeUrl(pathlib.relative(rootDir, scannedFileOrDirPath)), - title: name, - isDir: false - }; - - if (stats.isFile()) { - return item; - } - if (stats.isDirectory()) { - const scannedDir = scannedFileOrDirPath; - try { - const children = fs - .readdirSync(scannedDir) - .map(child => scanRecursive(rootDir, pathlib.join(scannedDir, child))) - .filter(e => !!e); - - item.isDir = true; - item.children = _.sortBy(children, i => !i.isDir); - - return item; - } catch (ex) { - if (ex.code === 'EACCES') { - logger.debug('cannot access directory, ignoring'); - } - return null; - } - } else { - return null; - } - } - - function scanArchive(archivePath) { - logger.debug('scaning archive', archivePath); - return scanRecursive(archivePath, archivePath); - } - - function browseArchiveTree(req, timestamp = Date.now()) { - const { blueprintId } = req.params; - const archiveUrl = `/blueprints/${blueprintId}/archive`; - logger.debug('download archive from url', archiveUrl); - - const archiveFolder = pathlib.join(browseSourcesDir, `${blueprintId}${timestamp}`); - return ArchiveHelper.removeOldExtracts(browseSourcesDir) - .then(() => ArchiveHelper.saveDataFromUrl(archiveUrl, archiveFolder, req)) - .then(data => { - const archivePath = pathlib.join(data.archiveFolder, data.archiveFile); - const extractedDir = pathlib.join(data.archiveFolder, blueprintExtractDir); - - return ArchiveHelper.decompressArchive(archivePath, extractedDir).then(() => ({ - ...scanArchive(extractedDir), - timestamp - })); - }); - } - - function checkPrefix(absCandidate, absPrefix) { - return absCandidate.substring(0, absPrefix.length) === absPrefix; - } - - async function browseArchiveFile(req, timestamp, path) { - const { blueprintId } = req.params; - const absolutePath = pathlib.resolve(browseSourcesDir, `${blueprintId}${timestamp}`, blueprintExtractDir, path); - - if (!checkPrefix(absolutePath, browseSourcesDir)) { - throw new Error('Wrong path'); - } - - if (!fs.existsSync(absolutePath)) { - // Cluster node may have changed, so fetch and extract blueprint archive if requested file does not exist - await browseArchiveTree(req, timestamp); - } - - return fs.readFile(absolutePath, 'utf-8'); - } - - function saveMultipartData(req) { - const targetPath = pathlib.join(lookupYamlsDir, `archive${Date.now()}`); - return ArchiveHelper.saveMultipartData(req, targetPath, 'archive'); - } - - function saveDataFromUrl(archiveUrl) { - const targetPath = pathlib.join(lookupYamlsDir, `archive${Date.now()}`); - return ArchiveHelper.saveDataFromUrl(archiveUrl, targetPath); - } - - function convertYamlToJson(path, yamlFile) { - let yamlFilePath = ''; - - let files = fs.readdirSync(path); - if (_.includes(files, yamlFile)) { - yamlFilePath = pathlib.resolve(path, yamlFile); - } else if (files.length === 1 && fs.statSync(pathlib.join(path, files[0])).isDirectory()) { - const directory = files[0]; - files = fs.readdirSync(pathlib.join(path, directory)); - if (_.includes(files, yamlFile)) { - yamlFilePath = pathlib.resolve(path, directory, yamlFile); - } - } - - if (!_.isEmpty(yamlFilePath)) { - try { - const json = yaml.safeLoad(fs.readFileSync(yamlFilePath, 'utf8')); - return Promise.resolve(json); - } catch (error) { - const errorMessage = `Cannot parse YAML file ${yamlFile}. Error: ${error}`; - logger.error(errorMessage); - return Promise.reject(errorMessage); - } - } else { - return Promise.reject(`Cannot find YAML file ${yamlFile} in specified directory.`); - } - } - - function getInputs(inputs) { - return _.mapValues(inputs, inputObject => inputObject || {}); - } - - function getPlugins(imports) { - const PLUGIN_KEYWORD = 'plugin:'; - - return _.chain(imports) - .filter(imp => String(imp).match(PLUGIN_KEYWORD)) - .map(plugin => { - const [packageName, pluginQueryString] = _.chain(plugin).replace(PLUGIN_KEYWORD, '').split('?').value(); - - const params = Utils.getParams(pluginQueryString); - const pluginObject = { packageName, params }; - - return pluginObject; - }) - .reduce((result, pluginObject) => { - result[pluginObject.packageName] = _.omit(pluginObject, 'packageName'); - return result; - }, {}) - .value(); - } - - function getSecrets(json) { - const SECRET_KEYWORD = 'get_secret'; - - return _.chain(Utils.getValuesWithPaths(json, SECRET_KEYWORD)) - .reduce((result, value) => { - const secretName = _.keys(value)[0]; - const secretPath = value[secretName]; - - if (_.isUndefined(result[secretName])) { - result[secretName] = {}; - } - - (result[secretName].paths || (result[secretName].paths = [])).push(secretPath); - - return result; - }, {}) - .value(); - } - - function getBlueprintArchiveContent(request) { - const { query } = request; - const promise = query.url ? saveDataFromUrl(query.url) : saveMultipartData(request); - - return promise.then(data => { - const { archiveFolder } = data; - const { archiveFile } = data; // filename with extension - const archiveFileName = pathlib.parse(archiveFile).name; // filename without extension - const extractedDir = pathlib.join(archiveFolder, 'extracted'); - - return ArchiveHelper.removeOldExtracts(lookupYamlsDir) - .then(() => { - if (_.isEmpty(archiveFile)) { - throw new Error('No archive file provided'); - } else { - const archivePath = pathlib.join(archiveFolder, archiveFile); - const archiveExtension = pathlib.parse(archiveFile).ext; // file extension - - if (archiveExtension === '.yml' || archiveExtension === '.yaml') { - return ArchiveHelper.storeSingleYamlFile(archivePath, archiveFile, extractedDir); - } - return ArchiveHelper.decompressArchive(archivePath, extractedDir); - } - }) - .then(decompressData => ({ - archiveFileName, - extractedDir, - decompressData - })) - .catch(err => { - ArchiveHelper.cleanTempData(archiveFolder); - throw err; - }); - }); - } - - function getBlueprintResources(request) { - const { query } = request; - const { yamlFile } = query; - - return getBlueprintArchiveContent(request) - .then(data => convertYamlToJson(data.extractedDir, yamlFile)) - .then(json => ({ - inputs: getInputs(json.inputs), - dataTypes: json.data_types, - plugins: getPlugins(json.imports), - secrets: getSecrets(json) - })); - } - - function scanYamlFiles(extractedDir) { - logger.debug('scaning yaml files from', extractedDir); - - let items = fs.readdirSync(extractedDir); - - if (items.length === 1 && fs.statSync(pathlib.join(extractedDir, items[0])).isDirectory()) { - items = fs.readdirSync(pathlib.join(extractedDir, items[0])); - } - - items = _.filter(items, item => item.endsWith('.yaml')); - - return Promise.resolve(items); - } - - function listYamlFiles(request) { - const { query } = request; - const includeFilename = query.includeFilename === 'true'; - let archiveFileName = ''; - - return getBlueprintArchiveContent(request) - .then(data => { - archiveFileName = data.archiveFileName; - return scanYamlFiles(data.extractedDir); - }) - .then(data => (includeFilename ? [archiveFileName, ...data] : data)); - } - - return { - browseArchiveTree, - browseArchiveFile, - getBlueprintResources, - listYamlFiles - }; -})(); diff --git a/backend/handler/SourceHandler.ts b/backend/handler/SourceHandler.ts new file mode 100644 index 0000000000..5b15c071c1 --- /dev/null +++ b/backend/handler/SourceHandler.ts @@ -0,0 +1,275 @@ +// @ts-nocheck File not migrated fully to TS + +import _ from 'lodash'; +import os from 'os'; +import fs from 'fs-extra'; +import pathlib from 'path'; +import url from 'url'; +import yaml from 'js-yaml'; + +import { getConfig } from '../config'; +import * as ArchiveHelper from './ArchiveHelper'; +import { getParams, getValuesWithPaths } from '../utils'; + +import { getLogger } from './LoggerHandler'; + +const logger = getLogger('SourceHandler'); + +const sourceConfig = getConfig().app.source; +const browseSourcesDir = pathlib.join(os.tmpdir(), sourceConfig.browseSourcesDir); +const lookupYamlsDir = pathlib.join(os.tmpdir(), sourceConfig.lookupYamlsDir); + +const blueprintExtractDir = 'extracted'; + +function isUnixHiddenPath(path) { + // eslint-disable-next-line no-useless-escape + return /(^|.\/)\.+[^\/\.]/g.test(path); +} + +function toRelativeUrl(relativePath) { + const absoluteUrl = url.pathToFileURL(relativePath); + const relativeUrl = absoluteUrl.pathname.substring(url.pathToFileURL('').pathname.length + 1); + return relativeUrl; +} + +function scanRecursive(rootDir, scannedFileOrDirPath) { + const stats = fs.statSync(scannedFileOrDirPath); + const name = pathlib.basename(scannedFileOrDirPath); + + if (stats.isSymbolicLink() || isUnixHiddenPath(name)) { + return null; + } + + const item = { + key: toRelativeUrl(pathlib.relative(rootDir, scannedFileOrDirPath)), + title: name, + isDir: false + }; + + if (stats.isFile()) { + return item; + } + if (stats.isDirectory()) { + const scannedDir = scannedFileOrDirPath; + try { + const children = fs + .readdirSync(scannedDir) + .map(child => scanRecursive(rootDir, pathlib.join(scannedDir, child))) + .filter(e => !!e); + + item.isDir = true; + item.children = _.sortBy(children, i => !i.isDir); + + return item; + } catch (ex) { + if (ex.code === 'EACCES') { + logger.debug('cannot access directory, ignoring'); + } + return null; + } + } else { + return null; + } +} + +function scanArchive(archivePath) { + logger.debug('scaning archive', archivePath); + return scanRecursive(archivePath, archivePath); +} + +export function browseArchiveTree(req, timestamp = Date.now()) { + const { blueprintId } = req.params; + const archiveUrl = `/blueprints/${blueprintId}/archive`; + logger.debug('download archive from url', archiveUrl); + + const archiveFolder = pathlib.join(browseSourcesDir, `${blueprintId}${timestamp}`); + return ArchiveHelper.removeOldExtracts(browseSourcesDir) + .then(() => ArchiveHelper.saveDataFromUrl(archiveUrl, archiveFolder, req)) + .then(data => { + const archivePath = pathlib.join(data.archiveFolder, data.archiveFile); + const extractedDir = pathlib.join(data.archiveFolder, blueprintExtractDir); + + return ArchiveHelper.decompressArchive(archivePath, extractedDir).then(() => ({ + ...scanArchive(extractedDir), + timestamp + })); + }); +} + +function checkPrefix(absCandidate, absPrefix) { + return absCandidate.substring(0, absPrefix.length) === absPrefix; +} + +export async function browseArchiveFile(req, timestamp, path) { + const { blueprintId } = req.params; + const absolutePath = pathlib.resolve(browseSourcesDir, `${blueprintId}${timestamp}`, blueprintExtractDir, path); + + if (!checkPrefix(absolutePath, browseSourcesDir)) { + throw new Error('Wrong path'); + } + + if (!fs.existsSync(absolutePath)) { + // Cluster node may have changed, so fetch and extract blueprint archive if requested file does not exist + await browseArchiveTree(req, timestamp); + } + + return fs.readFile(absolutePath, 'utf-8'); +} + +function saveMultipartData(req) { + const targetPath = pathlib.join(lookupYamlsDir, `archive${Date.now()}`); + return ArchiveHelper.saveMultipartData(req, targetPath, 'archive'); +} + +function saveDataFromUrl(archiveUrl) { + const targetPath = pathlib.join(lookupYamlsDir, `archive${Date.now()}`); + return ArchiveHelper.saveDataFromUrl(archiveUrl, targetPath); +} + +function convertYamlToJson(path, yamlFile) { + let yamlFilePath = ''; + + let files = fs.readdirSync(path); + if (_.includes(files, yamlFile)) { + yamlFilePath = pathlib.resolve(path, yamlFile); + } else if (files.length === 1 && fs.statSync(pathlib.join(path, files[0])).isDirectory()) { + const directory = files[0]; + files = fs.readdirSync(pathlib.join(path, directory)); + if (_.includes(files, yamlFile)) { + yamlFilePath = pathlib.resolve(path, directory, yamlFile); + } + } + + if (!_.isEmpty(yamlFilePath)) { + try { + const json = yaml.safeLoad(fs.readFileSync(yamlFilePath, 'utf8')); + return Promise.resolve(json); + } catch (error) { + const errorMessage = `Cannot parse YAML file ${yamlFile}. Error: ${error}`; + logger.error(errorMessage); + return Promise.reject(errorMessage); + } + } else { + return Promise.reject(`Cannot find YAML file ${yamlFile} in specified directory.`); + } +} + +function getInputs(inputs) { + return _.mapValues(inputs, inputObject => inputObject || {}); +} + +function getPlugins(imports) { + const PLUGIN_KEYWORD = 'plugin:'; + + return _.chain(imports) + .filter(imp => String(imp).match(PLUGIN_KEYWORD)) + .map(plugin => { + const [packageName, pluginQueryString] = _.chain(plugin).replace(PLUGIN_KEYWORD, '').split('?').value(); + + const params = getParams(pluginQueryString); + const pluginObject = { packageName, params }; + + return pluginObject; + }) + .reduce((result, pluginObject) => { + result[pluginObject.packageName] = _.omit(pluginObject, 'packageName'); + return result; + }, {}) + .value(); +} + +function getSecrets(json) { + const SECRET_KEYWORD = 'get_secret'; + + return _.chain(getValuesWithPaths(json, SECRET_KEYWORD)) + .reduce((result, value) => { + const secretName = _.keys(value)[0]; + const secretPath = value[secretName]; + + if (_.isUndefined(result[secretName])) { + result[secretName] = {}; + } + + (result[secretName].paths || (result[secretName].paths = [])).push(secretPath); + + return result; + }, {}) + .value(); +} + +function getBlueprintArchiveContent(request) { + const { query } = request; + const promise = query.url ? saveDataFromUrl(query.url) : saveMultipartData(request); + + return promise.then(data => { + const { archiveFolder } = data; + const { archiveFile } = data; // filename with extension + const archiveFileName = pathlib.parse(archiveFile).name; // filename without extension + const extractedDir = pathlib.join(archiveFolder, 'extracted'); + + return ArchiveHelper.removeOldExtracts(lookupYamlsDir) + .then(() => { + if (_.isEmpty(archiveFile)) { + throw new Error('No archive file provided'); + } else { + const archivePath = pathlib.join(archiveFolder, archiveFile); + const archiveExtension = pathlib.parse(archiveFile).ext; // file extension + + if (archiveExtension === '.yml' || archiveExtension === '.yaml') { + return ArchiveHelper.storeSingleYamlFile(archivePath, archiveFile, extractedDir); + } + return ArchiveHelper.decompressArchive(archivePath, extractedDir); + } + }) + .then(decompressData => ({ + archiveFileName, + extractedDir, + decompressData + })) + .catch(err => { + ArchiveHelper.cleanTempData(archiveFolder); + throw err; + }); + }); +} + +export function getBlueprintResources(request) { + const { query } = request; + const { yamlFile } = query; + + return getBlueprintArchiveContent(request) + .then(data => convertYamlToJson(data.extractedDir, yamlFile)) + .then(json => ({ + inputs: getInputs(json.inputs), + dataTypes: json.data_types, + plugins: getPlugins(json.imports), + secrets: getSecrets(json) + })); +} + +function scanYamlFiles(extractedDir) { + logger.debug('scaning yaml files from', extractedDir); + + let items = fs.readdirSync(extractedDir); + + if (items.length === 1 && fs.statSync(pathlib.join(extractedDir, items[0])).isDirectory()) { + items = fs.readdirSync(pathlib.join(extractedDir, items[0])); + } + + items = _.filter(items, item => item.endsWith('.yaml')); + + return Promise.resolve(items); +} + +export function listYamlFiles(request) { + const { query } = request; + const includeFilename = query.includeFilename === 'true'; + let archiveFileName = ''; + + return getBlueprintArchiveContent(request) + .then(data => { + archiveFileName = data.archiveFileName; + return scanYamlFiles(data.extractedDir); + }) + .then(data => (includeFilename ? [archiveFileName, ...data] : data)); +} diff --git a/backend/handler/TemplateHandler.js b/backend/handler/TemplateHandler.js deleted file mode 100644 index c10e3bae9a..0000000000 --- a/backend/handler/TemplateHandler.js +++ /dev/null @@ -1,369 +0,0 @@ -/** - * Created by pposel on 24/02/2017. - */ - -const fs = require('fs-extra'); -const pathlib = require('path'); -const mkdirp = require('mkdirp'); -const _ = require('lodash'); -const moment = require('moment'); - -const ServerSettings = require('../serverSettings'); -const Utils = require('../utils'); -const AuthHandler = require('./AuthHandler'); - -const logger = require('./LoggerHandler').getLogger('TemplateHandler'); - -const builtInTemplatesFolder = Utils.getResourcePath('templates', false); -const userTemplatesFolder = Utils.getResourcePath('templates', true); -const builtInPagesFolder = pathlib.resolve(builtInTemplatesFolder, 'pages'); -const userPagesFolder = pathlib.resolve(userTemplatesFolder, 'pages'); - -const allTenants = '*'; - -module.exports = (() => { - function getTemplates(folder, isCustom, filter) { - const compareTemplates = (templateA, templateB) => { - const conflictingTemplates = - !_.isEmpty(_.intersection(templateA.data.roles, templateB.data.roles)) && - !_.isEmpty(_.intersection(templateA.data.tenants, templateB.data.tenants)); - if (conflictingTemplates) { - logger.warn( - `Templates '${templateA.id}' and '${templateB.id}' are in conflict (roles and tenants). ` + - `Not including '${templateA.id}'.` - ); - } - return conflictingTemplates; - }; - - return _.chain(fs.readdirSync(pathlib.resolve(folder))) - .filter(fileName => fs.lstatSync(pathlib.resolve(folder, fileName)).isFile() && filter(fileName)) - .map(templateFile => { - const templateFilePath = pathlib.resolve(folder, templateFile); - - try { - const pageFileContent = fs.readJsonSync(templateFilePath); - const id = pathlib.basename(templateFile, '.json'); - - const name = _.get(pageFileContent, 'name', id); - const updatedBy = _.get(pageFileContent, 'updatedBy', isCustom ? '' : 'Manager'); - const updatedAt = _.get(pageFileContent, 'updatedAt', ''); - const data = { - roles: _.get(pageFileContent, 'roles', []), - tenants: _.get(pageFileContent, 'tenants', []) - }; - - return { id, name, custom: isCustom, data, updatedBy, updatedAt }; - } catch (error) { - logger.error(`Error when trying to parse ${templateFilePath} file to JSON.`, error); - - return null; - } - }) - .uniqWith(compareTemplates) - .value(); - } - - function getUserTemplates() { - return getTemplates(userTemplatesFolder, true, () => true); - } - - function getBuiltInTemplates() { - const { mode } = ServerSettings.settings; - return getTemplates(builtInTemplatesFolder, false, fileName => _.startsWith(pathlib.basename(fileName), mode)); - } - - function listTemplates() { - return Promise.resolve(_.concat(getBuiltInTemplates(), getUserTemplates())); - } - - function getPages(folder, isCustom) { - return _.chain(fs.readdirSync(pathlib.resolve(folder))) - .map(pageFile => { - const pageFilePath = pathlib.resolve(folder, pageFile); - - try { - const pageFileContent = fs.readJsonSync(pageFilePath); - const id = pathlib.basename(pageFile, '.json'); - - const name = _.get(pageFileContent, 'name', id); - const updatedBy = _.get(pageFileContent, 'updatedBy', isCustom ? '' : 'Manager'); - const updatedAt = _.get(pageFileContent, 'updatedAt', ''); - - return { id, name, custom: isCustom, updatedBy, updatedAt }; - } catch (error) { - logger.error(`Error when trying to parse ${pageFilePath} file to JSON.`, error); - - return null; - } - }) - .reject(_.isNull) - .value(); - } - - function getUserPages() { - return getPages(userPagesFolder, true); - } - - function getBuiltInPages() { - return getPages(builtInPagesFolder, false); - } - - function getHighestRole(userRoles, allRoles) { - return _.get( - _.find(allRoles, role => _.includes(userRoles, role.name)), - 'name', - null - ); - } - - async function getRole(userSystemRole, groupSystemRoles, tenantsRoles, tenant, token) { - const rbac = await AuthHandler.getRBAC(token); - const { roles } = rbac; - const userRoles = _.compact( - _.concat(_.get(tenantsRoles[tenant], 'roles', []), userSystemRole, _.keys(groupSystemRoles)) - ); - - logger.debug( - `Inputs for role calculation: systemRole=${userSystemRole}, tenant=${tenant}, tenantsRoles=${JSON.stringify( - tenantsRoles - )}, userRoles=${JSON.stringify(userRoles)}` - ); - - const role = getHighestRole(userRoles, roles); - logger.debug(`Calculated role: ${role}`); - return role; - } - - async function getSystemRole(userSystemRole, groupSystemRoles, token) { - const rbac = await AuthHandler.getRBAC(token); - const { roles } = rbac; - const systemRoles = [userSystemRole, ..._.keys(groupSystemRoles)]; - - logger.debug( - `Inputs for system role calculation: userSystemRole=${userSystemRole}, groupSystemRoles=${JSON.stringify( - groupSystemRoles - )}` - ); - - const systemRole = getHighestRole(systemRoles, roles); - logger.debug(`Calculated system role: ${systemRole}`); - return systemRole; - } - - function listPages() { - return Promise.resolve(_.concat(getBuiltInPages(), getUserPages())); - } - - function checkTemplateExistence(data, excludeTemplateId) { - const { roles } = data; - const { tenants } = data; - const getTenantString = tenant => (tenant === allTenants ? 'all tenants' : `tenant=${tenant}`); - - const userTemplates = _.filter(getUserTemplates(), template => template.id !== excludeTemplateId); - - logger.debug( - `Checking template existence for roles=${roles} and ${getTenantString(tenants)}.` + - `${excludeTemplateId ? ` Excluding: '${excludeTemplateId}' template.` : ''}` - ); - - const conflictingTemplate = _.filter( - userTemplates, - userTemplate => - !_.isEmpty(_.intersection(roles, userTemplate.data.roles)) && - !_.isEmpty(_.intersection(tenants, userTemplate.data.tenants)) - ); - - if (!_.isEmpty(conflictingTemplate)) { - return Promise.reject( - `Template for specified role(s) and tenant(s) already exists: '${conflictingTemplate[0].id}'.` - ); - } - return Promise.resolve(); - } - - function createTemplate(username, template) { - const path = pathlib.resolve(userTemplatesFolder, `${template.id}.json`); - if (fs.existsSync(path)) { - return Promise.reject(`Template name "${template.id}" already exists`); - } - - const content = { - updatedBy: username, - updatedAt: moment().format(), - roles: template.data.roles, - tenants: template.data.tenants, - pages: template.pages - }; - - return checkTemplateExistence(template.data).then(() => fs.writeJson(path, content, { spaces: ' ' })); - } - - function deleteTemplate(templateId) { - const path = pathlib.resolve(userTemplatesFolder, `${templateId}.json`); - - return new Promise((resolve, reject) => { - fs.remove(path, err => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); - } - - function updateTemplate(username, template) { - const path = pathlib.resolve(userTemplatesFolder, `${template.id}.json`); - - const content = { - updatedBy: username, - updatedAt: moment().format(), - roles: template.data.roles, - tenants: template.data.tenants, - pages: template.pages - }; - - return checkTemplateExistence(template.data, template.oldId) - .then( - () => - new Promise((resolve, reject) => { - if (template.oldId && template.id !== template.oldId) { - if (fs.existsSync(path)) { - reject(`Template name "${template.id}" already exists`); - } else { - deleteTemplate(template.oldId) - .then(() => resolve()) - .catch(error => reject(error)); - } - } else { - resolve(); - } - }) - ) - .then(() => fs.writeJson(path, content, { spaces: ' ' })); - } - - function createPage(username, page) { - const path = pathlib.resolve(userPagesFolder, `${page.id}.json`); - if (fs.existsSync(path)) { - return Promise.reject(`Page id "${page.id}" already exists`); - } - - const content = { - ..._.pick(page, 'name', 'layout'), - updatedBy: username, - updatedAt: moment().format() - }; - - return fs.writeJson(path, content, { spaces: ' ' }); - } - - function deletePage(pageId) { - const path = pathlib.resolve(userPagesFolder, `${pageId}.json`); - - return new Promise((resolve, reject) => { - fs.remove(path, err => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); - } - - function updatePage(username, page) { - const path = pathlib.resolve(userPagesFolder, `${page.id}.json`); - - const content = { - ..._.omit(page, 'id'), - updatedBy: username, - updatedAt: moment().format() - }; - - return new Promise((resolve, reject) => { - if (page.oldId && page.id !== page.oldId) { - if (fs.existsSync(path)) { - reject(`Page name "${page.id}" already exists`); - } else { - deletePage(page.oldId) - .then(() => resolve()) - .catch(error => reject(error)); - } - } else { - resolve(); - } - }).then(() => fs.writeJson(path, content, { spaces: ' ' })); - } - - async function selectTemplate(userSystemRole, groupSystemRoles, tenantsRoles, tenant, token) { - const role = await getRole(userSystemRole, groupSystemRoles, tenantsRoles, tenant, token); - const systemRole = await getSystemRole(userSystemRole, groupSystemRoles, token); - const { mode } = ServerSettings.settings; - let templateId = null; - - logger.debug(`Template inputs: mode=${mode}, role=${role}, systemRole=${systemRole}, tenant=${tenant}`); - - // Search user template - if (mode === ServerSettings.MODE_MAIN) { - const userTemplates = getUserTemplates(); - const matchingTemplateForSpecificTenant = _.find( - userTemplates, - template => _.includes(template.data.roles, role) && _.includes(template.data.tenants, tenant) - ); - - if (!matchingTemplateForSpecificTenant) { - const matchingTemplateForAllTenants = _.find( - userTemplates, - template => _.includes(template.data.roles, role) && _.includes(template.data.tenants, allTenants) - ); - templateId = _.get(matchingTemplateForAllTenants, 'id', null); - } else { - templateId = matchingTemplateForSpecificTenant.id; - } - } - - // Search built-in template - if (!templateId) { - if (mode === ServerSettings.MODE_MAIN) { - templateId = `${mode}-${systemRole}`; - } else { - templateId = mode; - } - logger.debug(`No user template found. Using: ${templateId}`); - } else { - logger.debug(`User template found: ${templateId}`); - } - - return templateId; - } - - function init() { - return new Promise((resolve, reject) => { - try { - logger.info('Setting up user templates directory:', userTemplatesFolder); - mkdirp.sync(userTemplatesFolder); - logger.info('Setting up user pages directory:', userPagesFolder); - mkdirp.sync(userPagesFolder); - return resolve(); - } catch (e) { - logger.error('Could not set up directories for templates and pages, error was:', e); - return reject(`Could not set up directories for templates and pages, error was: ${e}`); - } - }); - } - - return { - init, - listTemplates, - listPages, - createTemplate, - updateTemplate, - deleteTemplate, - selectTemplate, - createPage, - updatePage, - deletePage - }; -})(); diff --git a/backend/handler/TemplateHandler.ts b/backend/handler/TemplateHandler.ts new file mode 100644 index 0000000000..f153ea7abc --- /dev/null +++ b/backend/handler/TemplateHandler.ts @@ -0,0 +1,354 @@ +// @ts-nocheck File not migrated fully to TS + +import fs from 'fs-extra'; +import pathlib from 'path'; +import mkdirp from 'mkdirp'; +import _ from 'lodash'; +import moment from 'moment'; + +import { getMode, MODE_MAIN } from '../serverSettings'; +import { getResourcePath } from '../utils'; +import { getRBAC } from './AuthHandler'; + +import { getLogger } from './LoggerHandler'; + +const logger = getLogger('TemplateHandler'); + +const builtInTemplatesFolder = getResourcePath('templates', false); +const userTemplatesFolder = getResourcePath('templates', true); +const builtInPagesFolder = pathlib.resolve(builtInTemplatesFolder, 'pages'); +const userPagesFolder = pathlib.resolve(userTemplatesFolder, 'pages'); + +const allTenants = '*'; + +function getTemplates(folder, isCustom, filter) { + const compareTemplates = (templateA, templateB) => { + const conflictingTemplates = + !_.isEmpty(_.intersection(templateA.data.roles, templateB.data.roles)) && + !_.isEmpty(_.intersection(templateA.data.tenants, templateB.data.tenants)); + if (conflictingTemplates) { + logger.warn( + `Templates '${templateA.id}' and '${templateB.id}' are in conflict (roles and tenants). ` + + `Not including '${templateA.id}'.` + ); + } + return conflictingTemplates; + }; + + return _.chain(fs.readdirSync(pathlib.resolve(folder))) + .filter(fileName => fs.lstatSync(pathlib.resolve(folder, fileName)).isFile() && filter(fileName)) + .map(templateFile => { + const templateFilePath = pathlib.resolve(folder, templateFile); + + try { + const pageFileContent = fs.readJsonSync(templateFilePath); + const id = pathlib.basename(templateFile, '.json'); + + const name = _.get(pageFileContent, 'name', id); + const updatedBy = _.get(pageFileContent, 'updatedBy', isCustom ? '' : 'Manager'); + const updatedAt = _.get(pageFileContent, 'updatedAt', ''); + const data = { + roles: _.get(pageFileContent, 'roles', []), + tenants: _.get(pageFileContent, 'tenants', []) + }; + + return { id, name, custom: isCustom, data, updatedBy, updatedAt }; + } catch (error) { + logger.error(`Error when trying to parse ${templateFilePath} file to JSON.`, error); + + return null; + } + }) + .uniqWith(compareTemplates) + .value(); +} + +function getUserTemplates() { + return getTemplates(userTemplatesFolder, true, () => true); +} + +function getBuiltInTemplates() { + const mode = getMode(); + return getTemplates(builtInTemplatesFolder, false, fileName => _.startsWith(pathlib.basename(fileName), mode)); +} + +export function listTemplates() { + return Promise.resolve(_.concat(getBuiltInTemplates(), getUserTemplates())); +} + +function getPages(folder, isCustom) { + return _.chain(fs.readdirSync(pathlib.resolve(folder))) + .map(pageFile => { + const pageFilePath = pathlib.resolve(folder, pageFile); + + try { + const pageFileContent = fs.readJsonSync(pageFilePath); + const id = pathlib.basename(pageFile, '.json'); + + const name = _.get(pageFileContent, 'name', id); + const updatedBy = _.get(pageFileContent, 'updatedBy', isCustom ? '' : 'Manager'); + const updatedAt = _.get(pageFileContent, 'updatedAt', ''); + + return { id, name, custom: isCustom, updatedBy, updatedAt }; + } catch (error) { + logger.error(`Error when trying to parse ${pageFilePath} file to JSON.`, error); + + return null; + } + }) + .reject(_.isNull) + .value(); +} + +function getUserPages() { + return getPages(userPagesFolder, true); +} + +function getBuiltInPages() { + return getPages(builtInPagesFolder, false); +} + +function getHighestRole(userRoles, allRoles) { + return _.get( + _.find(allRoles, role => _.includes(userRoles, role.name)), + 'name', + null + ); +} + +async function getRole(userSystemRole, groupSystemRoles, tenantsRoles, tenant, token) { + const rbac = await getRBAC(token); + const { roles } = rbac; + const userRoles = _.compact( + _.concat(_.get(tenantsRoles[tenant], 'roles', []), userSystemRole, _.keys(groupSystemRoles)) + ); + + logger.debug( + `Inputs for role calculation: systemRole=${userSystemRole}, tenant=${tenant}, tenantsRoles=${JSON.stringify( + tenantsRoles + )}, userRoles=${JSON.stringify(userRoles)}` + ); + + const role = getHighestRole(userRoles, roles); + logger.debug(`Calculated role: ${role}`); + return role; +} + +async function getSystemRole(userSystemRole, groupSystemRoles, token) { + const rbac = await getRBAC(token); + const { roles } = rbac; + const systemRoles = [userSystemRole, ..._.keys(groupSystemRoles)]; + + logger.debug( + `Inputs for system role calculation: userSystemRole=${userSystemRole}, groupSystemRoles=${JSON.stringify( + groupSystemRoles + )}` + ); + + const systemRole = getHighestRole(systemRoles, roles); + logger.debug(`Calculated system role: ${systemRole}`); + return systemRole; +} + +export function listPages() { + return Promise.resolve(_.concat(getBuiltInPages(), getUserPages())); +} + +function checkTemplateExistence(data, excludeTemplateId) { + const { roles } = data; + const { tenants } = data; + const getTenantString = tenant => (tenant === allTenants ? 'all tenants' : `tenant=${tenant}`); + + const userTemplates = _.filter(getUserTemplates(), template => template.id !== excludeTemplateId); + + logger.debug( + `Checking template existence for roles=${roles} and ${getTenantString(tenants)}.` + + `${excludeTemplateId ? ` Excluding: '${excludeTemplateId}' template.` : ''}` + ); + + const conflictingTemplate = _.filter( + userTemplates, + userTemplate => + !_.isEmpty(_.intersection(roles, userTemplate.data.roles)) && + !_.isEmpty(_.intersection(tenants, userTemplate.data.tenants)) + ); + + if (!_.isEmpty(conflictingTemplate)) { + return Promise.reject( + `Template for specified role(s) and tenant(s) already exists: '${conflictingTemplate[0].id}'.` + ); + } + return Promise.resolve(); +} + +export function createTemplate(username, template) { + const path = pathlib.resolve(userTemplatesFolder, `${template.id}.json`); + if (fs.existsSync(path)) { + return Promise.reject(`Template name "${template.id}" already exists`); + } + + const content = { + updatedBy: username, + updatedAt: moment().format(), + roles: template.data.roles, + tenants: template.data.tenants, + pages: template.pages + }; + + return checkTemplateExistence(template.data).then(() => fs.writeJson(path, content, { spaces: ' ' })); +} + +export function deleteTemplate(templateId) { + const path = pathlib.resolve(userTemplatesFolder, `${templateId}.json`); + + return new Promise((resolve, reject) => { + fs.remove(path, err => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); +} + +export function updateTemplate(username, template) { + const path = pathlib.resolve(userTemplatesFolder, `${template.id}.json`); + + const content = { + updatedBy: username, + updatedAt: moment().format(), + roles: template.data.roles, + tenants: template.data.tenants, + pages: template.pages + }; + + return checkTemplateExistence(template.data, template.oldId) + .then( + () => + new Promise((resolve, reject) => { + if (template.oldId && template.id !== template.oldId) { + if (fs.existsSync(path)) { + reject(`Template name "${template.id}" already exists`); + } else { + deleteTemplate(template.oldId) + .then(() => resolve()) + .catch(error => reject(error)); + } + } else { + resolve(); + } + }) + ) + .then(() => fs.writeJson(path, content, { spaces: ' ' })); +} + +export function createPage(username, page) { + const path = pathlib.resolve(userPagesFolder, `${page.id}.json`); + if (fs.existsSync(path)) { + return Promise.reject(`Page id "${page.id}" already exists`); + } + + const content = { + ..._.pick(page, 'name', 'layout'), + updatedBy: username, + updatedAt: moment().format() + }; + + return fs.writeJson(path, content, { spaces: ' ' }); +} + +export function deletePage(pageId) { + const path = pathlib.resolve(userPagesFolder, `${pageId}.json`); + + return new Promise((resolve, reject) => { + fs.remove(path, err => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); +} + +export function updatePage(username, page) { + const path = pathlib.resolve(userPagesFolder, `${page.id}.json`); + + const content = { + ..._.omit(page, 'id'), + updatedBy: username, + updatedAt: moment().format() + }; + + return new Promise((resolve, reject) => { + if (page.oldId && page.id !== page.oldId) { + if (fs.existsSync(path)) { + reject(`Page name "${page.id}" already exists`); + } else { + deletePage(page.oldId) + .then(() => resolve()) + .catch(error => reject(error)); + } + } else { + resolve(); + } + }).then(() => fs.writeJson(path, content, { spaces: ' ' })); +} + +export async function selectTemplate(userSystemRole, groupSystemRoles, tenantsRoles, tenant, token) { + const role = await getRole(userSystemRole, groupSystemRoles, tenantsRoles, tenant, token); + const systemRole = await getSystemRole(userSystemRole, groupSystemRoles, token); + const mode = getMode(); + let templateId = null; + + logger.debug(`Template inputs: mode=${mode}, role=${role}, systemRole=${systemRole}, tenant=${tenant}`); + + // Search user template + if (mode === MODE_MAIN) { + const userTemplates = getUserTemplates(); + const matchingTemplateForSpecificTenant = _.find( + userTemplates, + template => _.includes(template.data.roles, role) && _.includes(template.data.tenants, tenant) + ); + + if (!matchingTemplateForSpecificTenant) { + const matchingTemplateForAllTenants = _.find( + userTemplates, + template => _.includes(template.data.roles, role) && _.includes(template.data.tenants, allTenants) + ); + templateId = _.get(matchingTemplateForAllTenants, 'id', null); + } else { + templateId = matchingTemplateForSpecificTenant.id; + } + } + + // Search built-in template + if (!templateId) { + if (mode === MODE_MAIN) { + templateId = `${mode}-${systemRole}`; + } else { + templateId = mode; + } + logger.debug(`No user template found. Using: ${templateId}`); + } else { + logger.debug(`User template found: ${templateId}`); + } + + return templateId; +} + +export function init() { + return new Promise((resolve, reject) => { + try { + logger.info('Setting up user templates directory:', userTemplatesFolder); + mkdirp.sync(userTemplatesFolder); + logger.info('Setting up user pages directory:', userPagesFolder); + mkdirp.sync(userPagesFolder); + return resolve(); + } catch (e) { + logger.error('Could not set up directories for templates and pages, error was:', e); + return reject(`Could not set up directories for templates and pages, error was: ${e}`); + } + }); +} diff --git a/backend/handler/WidgetHandler.js b/backend/handler/WidgetHandler.js deleted file mode 100644 index 34f7d955a2..0000000000 --- a/backend/handler/WidgetHandler.js +++ /dev/null @@ -1,318 +0,0 @@ -/** - * Created by pposel on 24/02/2017. - */ - -const os = require('os'); -const fs = require('fs-extra'); -const pathlib = require('path'); -const mkdirp = require('mkdirp'); -const _ = require('lodash'); -const { db } = require('../db/Connection'); - -const logger = require('./LoggerHandler').getLogger('WidgetHandler'); - -const config = require('../config').get(); -const Utils = require('../utils'); -const ArchiveHelper = require('./ArchiveHelper'); -const BackendHandler = require('./BackendHandler'); - -const builtInWidgetsFolder = Utils.getResourcePath('widgets', false); -const userWidgetsFolder = Utils.getResourcePath('widgets', true); -const widgetTempDir = pathlib.join(os.tmpdir(), config.app.widgets.tempDir); - -module.exports = (() => { - function saveMultipartData(req) { - const targetPath = pathlib.join(widgetTempDir, `widget${Date.now()}`); - return ArchiveHelper.saveMultipartData(req, targetPath, 'widget'); - } - - function saveDataFromUrl(archiveUrl) { - const targetPath = pathlib.join(widgetTempDir, `widget${Date.now()}`); - return ArchiveHelper.saveDataFromUrl(archiveUrl, targetPath); - } - - // Credits to: https://geedew.com/remove-a-directory-that-is-not-empty-in-nodejs/ - - function rmdirSync(path) { - if (fs.existsSync(path)) { - fs.readdirSync(path).forEach(file => { - const curPath = `${path}/${file}`; - if (fs.lstatSync(curPath).isDirectory()) { - // recurse - rmdirSync(curPath); - } else { - // delete file - fs.unlinkSync(curPath); - } - }); - fs.rmdirSync(path); - } - } - - function getUserWidgets() { - return fs - .readdirSync(pathlib.resolve(userWidgetsFolder)) - .filter(dir => fs.lstatSync(pathlib.resolve(userWidgetsFolder, dir)).isDirectory()); - } - - function getBuiltInWidgets() { - return fs - .readdirSync(pathlib.resolve(builtInWidgetsFolder)) - .filter( - dir => - fs.lstatSync(pathlib.resolve(builtInWidgetsFolder, dir)).isDirectory() && - _.indexOf(config.app.widgets.ignoreFolders, dir) < 0 - ); - } - - function getAllWidgets() { - return _.concat(getBuiltInWidgets(), getUserWidgets()); - } - - function validateUniqueness(widgetId) { - logger.debug(`Validating widget ${widgetId} uniqueness.`); - - const widgets = getAllWidgets(); - if (_.indexOf(widgets, widgetId) >= 0) { - return Promise.reject({ status: 422, message: `Widget ${widgetId} is already installed` }); - } - - return Promise.resolve(); - } - - function validateConsistency(widgetId, dirName) { - logger.debug(`Validating widget ${widgetId} consistency.`); - - if (widgetId !== dirName) { - return Promise.reject({ - status: 400, - message: `Updated widget directory name invalid. Expected: '${widgetId}'. Received: '${dirName}'` - }); - } - - return Promise.resolve(); - } - - function validateWidget(widgetId, extractedDir) { - logger.debug(`Validating widget ${widgetId}.`); - - let files = fs.readdirSync(extractedDir); - let tempPath = extractedDir; - - // remove hidden or junk files - files = _.filter(files, file => { - return !file.match(/^\..+/) && file !== '__MACOSX'; - }); - - if (files.length === 1 && fs.statSync(pathlib.join(extractedDir, files[0])).isDirectory()) { - const dirName = files[0]; - if (dirName !== widgetId) { - return Promise.reject({ - status: 400, - message: `Incorrect widget folder name not consistent with widget id. Widget ID: '${widgetId}'. Directory name: '${dirName}'` - }); - } - - tempPath = pathlib.join(extractedDir, dirName); - files = fs.readdirSync(tempPath); - } - - const { requiredFiles } = config.app.widgets; - const missingFiles = _.difference(requiredFiles, files); - - if (!_.isEmpty(missingFiles)) { - return Promise.reject({ - status: 400, - message: `The following files are required for widget registration: ${_.join(missingFiles, ', ')}` - }); - } - return Promise.resolve(tempPath); - } - - function installFiles(widgetId, tempPath) { - logger.debug('Installing widget files to the target path:', pathlib.resolve(userWidgetsFolder)); - logger.debug('Widget temp path:', tempPath); - - const installPath = pathlib.resolve(userWidgetsFolder, widgetId); - - return new Promise((resolve, reject) => { - rmdirSync(installPath); - fs.move(tempPath, installPath, err => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); - } - - function backupWidget(widgetId, tempPath) { - const installPath = pathlib.resolve(userWidgetsFolder, widgetId); - const backupPath = pathlib.resolve(tempPath, 'backup'); - - logger.debug(`Creating backup of widget ${widgetId} in ${backupPath}`); - return new Promise((resolve, reject) => { - fs.copy(installPath, backupPath, err => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); - } - - function restoreBackup(widgetId, tempPath) { - const installPath = pathlib.resolve(userWidgetsFolder, widgetId); - const backupPath = pathlib.resolve(tempPath, 'backup'); - - logger.debug(`Restoring backup of widget ${widgetId} from ${backupPath}`); - return new Promise((resolve, reject) => { - fs.removeSync(installPath); - fs.move(backupPath, installPath, err => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); - } - - function deleteWidget(widgetId) { - const path = pathlib.resolve(userWidgetsFolder, widgetId); - - logger.debug(`Deleting widget ${widgetId} from ${path}`); - return new Promise((resolve, reject) => { - fs.remove(path, err => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }).then(() => BackendHandler.removeWidgetBackend(widgetId)); - } - - function installWidget(archiveUrl, username, req) { - logger.debug('Installing widget from', archiveUrl || 'file'); - - return ArchiveHelper.removeOldExtracts(widgetTempDir) - .then(() => (archiveUrl ? saveDataFromUrl(archiveUrl) : saveMultipartData(req))) - .then(data => { - const widgetTempPath = data.archiveFolder; - const widgetZipFile = data.archiveFile; - const widgetId = pathlib.parse(pathlib.parse(widgetZipFile).name).name; - const archivePath = pathlib.join(widgetTempPath, widgetZipFile); - const extractedDir = pathlib.join(widgetTempPath, widgetId); - - return validateUniqueness(widgetId) - .then(() => ArchiveHelper.decompressArchive(archivePath, extractedDir)) - .then(() => validateWidget(widgetId, extractedDir)) - .then(tempPath => installFiles(widgetId, tempPath)) - .then(() => BackendHandler.importWidgetBackend(widgetId)) - .then(() => { - const widgetPath = pathlib.resolve(userWidgetsFolder, widgetId); - - logger.info('New widget installed'); - logger.info('Widget id: ', widgetId); - logger.info('Widget path: ', pathlib.resolve(widgetPath)); - - ArchiveHelper.cleanTempData(widgetTempPath); - - return Promise.resolve({ id: widgetId, isCustom: true }); - }) - .catch(err => { - logger.error(`Error during widget ${widgetId} installation: ${err}`); - deleteWidget(widgetId); - ArchiveHelper.cleanTempData(widgetTempPath); - throw err; - }); - }); - } - - function updateWidget(updateWidgetId, archiveUrl, req) { - logger.debug('Updating widget', updateWidgetId, 'from', archiveUrl || 'file'); - - return ArchiveHelper.removeOldExtracts(widgetTempDir) - .then(() => (archiveUrl ? saveDataFromUrl(archiveUrl) : saveMultipartData(req))) - .then(data => { - const widgetTempPath = data.archiveFolder; - const widgetZipFile = data.archiveFile; - const widgetId = pathlib.parse(pathlib.parse(widgetZipFile).name).name; - const archivePath = pathlib.join(widgetTempPath, widgetZipFile); - const extractedDir = pathlib.join(widgetTempPath, widgetId); - - return backupWidget(updateWidgetId, widgetTempPath) - .then(() => validateConsistency(updateWidgetId, widgetId)) - .then(() => ArchiveHelper.decompressArchive(archivePath, extractedDir)) - .then(() => validateWidget(widgetId, extractedDir)) - .then(tempPath => installFiles(widgetId, tempPath)) - .then(() => BackendHandler.removeWidgetBackend(widgetId)) - .then(() => BackendHandler.importWidgetBackend(widgetId)) - .then(() => { - const widgetPath = pathlib.resolve(userWidgetsFolder, widgetId); - - logger.info('Widget updated'); - logger.info('Widget id:', widgetId); - logger.info('Widget path:', pathlib.resolve(widgetPath)); - - ArchiveHelper.cleanTempData(widgetTempPath); - - return Promise.resolve({ id: widgetId, isCustom: true }); - }) - .catch(err => { - logger.error(`Error during widget ${widgetId} update: ${err}`); - restoreBackup(updateWidgetId, widgetTempPath) - .then(() => BackendHandler.removeWidgetBackend(widgetId)) - .then(() => BackendHandler.importWidgetBackend(widgetId)) - .then(() => ArchiveHelper.cleanTempData(widgetTempPath)); - throw err; - }); - }); - } - function listWidgets() { - const builtInWidgets = _.map(getBuiltInWidgets(), widget => ({ id: widget, isCustom: false })); - const userWidgets = _.map(getUserWidgets(), widget => ({ id: widget, isCustom: true })); - - return Promise.resolve(_.concat(builtInWidgets, userWidgets)); - } - - function isWidgetUsed(widgetId) { - return db.UserApp.findAll({ attributes: ['appData', 'username'] }).then(userApp => { - const result = []; - _.forEach(userApp, row => { - const filter = _.filter(row.appData.pages, { widgets: [{ definition: widgetId }] }); - if (!_.isEmpty(filter)) { - // TODO(RD-1459) Refactor results to be just list of usernames - result.push({ username: row.username, managerIp: '' }); - } - }); - - return result; - }); - } - - function init() { - return new Promise((resolve, reject) => { - try { - logger.info('Setting up user widgets directory:', userWidgetsFolder); - mkdirp.sync(userWidgetsFolder); - return BackendHandler.initWidgetBackends().then(resolve).catch(reject); - } catch (e) { - logger.error('Could not set up directory, error was:', e); - return reject(`Could not set up directory, error was: ${e}`); - } - }); - } - - return { - init, - listWidgets, - installWidget, - isWidgetUsed, - deleteWidget, - updateWidget - }; -})(); diff --git a/backend/handler/WidgetHandler.ts b/backend/handler/WidgetHandler.ts new file mode 100644 index 0000000000..8888cbe531 --- /dev/null +++ b/backend/handler/WidgetHandler.ts @@ -0,0 +1,307 @@ +// @ts-nocheck File not migrated fully to TS + +import os from 'os'; +import fs from 'fs-extra'; +import pathlib from 'path'; +import mkdirp from 'mkdirp'; +import _ from 'lodash'; +import { db } from '../db/Connection'; + +import { getLogger } from './LoggerHandler'; + +import { getConfig } from '../config'; +import { getResourcePath } from '../utils'; +import * as ArchiveHelper from './ArchiveHelper'; +import * as BackendHandler from './BackendHandler'; + +const logger = getLogger('WidgetHandler'); + +const builtInWidgetsFolder = getResourcePath('widgets', false); +const userWidgetsFolder = getResourcePath('widgets', true); +const widgetTempDir = pathlib.join(os.tmpdir(), getConfig().app.widgets.tempDir); + +function saveMultipartData(req) { + const targetPath = pathlib.join(widgetTempDir, `widget${Date.now()}`); + return ArchiveHelper.saveMultipartData(req, targetPath, 'widget'); +} + +function saveDataFromUrl(archiveUrl) { + const targetPath = pathlib.join(widgetTempDir, `widget${Date.now()}`); + return ArchiveHelper.saveDataFromUrl(archiveUrl, targetPath); +} + +// Credits to: https://geedew.com/remove-a-directory-that-is-not-empty-in-nodejs/ + +function rmdirSync(path) { + if (fs.existsSync(path)) { + fs.readdirSync(path).forEach(file => { + const curPath = `${path}/${file}`; + if (fs.lstatSync(curPath).isDirectory()) { + // recurse + rmdirSync(curPath); + } else { + // delete file + fs.unlinkSync(curPath); + } + }); + fs.rmdirSync(path); + } +} + +function getUserWidgets() { + return fs + .readdirSync(pathlib.resolve(userWidgetsFolder)) + .filter(dir => fs.lstatSync(pathlib.resolve(userWidgetsFolder, dir)).isDirectory()); +} + +function getBuiltInWidgets() { + return fs + .readdirSync(pathlib.resolve(builtInWidgetsFolder)) + .filter( + dir => + fs.lstatSync(pathlib.resolve(builtInWidgetsFolder, dir)).isDirectory() && + _.indexOf(getConfig().app.widgets.ignoreFolders, dir) < 0 + ); +} + +function getAllWidgets() { + return _.concat(getBuiltInWidgets(), getUserWidgets()); +} + +function validateUniqueness(widgetId) { + logger.debug(`Validating widget ${widgetId} uniqueness.`); + + const widgets = getAllWidgets(); + if (_.indexOf(widgets, widgetId) >= 0) { + return Promise.reject({ status: 422, message: `Widget ${widgetId} is already installed` }); + } + + return Promise.resolve(); +} + +function validateConsistency(widgetId, dirName) { + logger.debug(`Validating widget ${widgetId} consistency.`); + + if (widgetId !== dirName) { + return Promise.reject({ + status: 400, + message: `Updated widget directory name invalid. Expected: '${widgetId}'. Received: '${dirName}'` + }); + } + + return Promise.resolve(); +} + +function validateWidget(widgetId, extractedDir) { + logger.debug(`Validating widget ${widgetId}.`); + + let files = fs.readdirSync(extractedDir); + let tempPath = extractedDir; + + // remove hidden or junk files + files = _.filter(files, file => { + return !file.match(/^\..+/) && file !== '__MACOSX'; + }); + + if (files.length === 1 && fs.statSync(pathlib.join(extractedDir, files[0])).isDirectory()) { + const dirName = files[0]; + if (dirName !== widgetId) { + return Promise.reject({ + status: 400, + message: `Incorrect widget folder name not consistent with widget id. Widget ID: '${widgetId}'. Directory name: '${dirName}'` + }); + } + + tempPath = pathlib.join(extractedDir, dirName); + files = fs.readdirSync(tempPath); + } + + const { requiredFiles } = getConfig().app.widgets; + const missingFiles = _.difference(requiredFiles, files); + + if (!_.isEmpty(missingFiles)) { + return Promise.reject({ + status: 400, + message: `The following files are required for widget registration: ${_.join(missingFiles, ', ')}` + }); + } + return Promise.resolve(tempPath); +} + +function installFiles(widgetId, tempPath) { + logger.debug('Installing widget files to the target path:', pathlib.resolve(userWidgetsFolder)); + logger.debug('Widget temp path:', tempPath); + + const installPath = pathlib.resolve(userWidgetsFolder, widgetId); + + return new Promise((resolve, reject) => { + rmdirSync(installPath); + fs.move(tempPath, installPath, err => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); +} + +function backupWidget(widgetId, tempPath) { + const installPath = pathlib.resolve(userWidgetsFolder, widgetId); + const backupPath = pathlib.resolve(tempPath, 'backup'); + + logger.debug(`Creating backup of widget ${widgetId} in ${backupPath}`); + return new Promise((resolve, reject) => { + fs.copy(installPath, backupPath, err => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); +} + +function restoreBackup(widgetId, tempPath) { + const installPath = pathlib.resolve(userWidgetsFolder, widgetId); + const backupPath = pathlib.resolve(tempPath, 'backup'); + + logger.debug(`Restoring backup of widget ${widgetId} from ${backupPath}`); + return new Promise((resolve, reject) => { + fs.removeSync(installPath); + fs.move(backupPath, installPath, err => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); +} + +export function deleteWidget(widgetId) { + const path = pathlib.resolve(userWidgetsFolder, widgetId); + + logger.debug(`Deleting widget ${widgetId} from ${path}`); + return new Promise((resolve, reject) => { + fs.remove(path, err => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }).then(() => BackendHandler.removeWidgetBackend(widgetId)); +} + +export function installWidget(archiveUrl, username, req) { + logger.debug('Installing widget from', archiveUrl || 'file'); + + return ArchiveHelper.removeOldExtracts(widgetTempDir) + .then(() => (archiveUrl ? saveDataFromUrl(archiveUrl) : saveMultipartData(req))) + .then(data => { + const widgetTempPath = data.archiveFolder; + const widgetZipFile = data.archiveFile; + const widgetId = pathlib.parse(pathlib.parse(widgetZipFile).name).name; + const archivePath = pathlib.join(widgetTempPath, widgetZipFile); + const extractedDir = pathlib.join(widgetTempPath, widgetId); + + return validateUniqueness(widgetId) + .then(() => ArchiveHelper.decompressArchive(archivePath, extractedDir)) + .then(() => validateWidget(widgetId, extractedDir)) + .then(tempPath => installFiles(widgetId, tempPath)) + .then(() => BackendHandler.importWidgetBackend(widgetId)) + .then(() => { + const widgetPath = pathlib.resolve(userWidgetsFolder, widgetId); + + logger.info('New widget installed'); + logger.info('Widget id: ', widgetId); + logger.info('Widget path: ', pathlib.resolve(widgetPath)); + + ArchiveHelper.cleanTempData(widgetTempPath); + + return Promise.resolve({ id: widgetId, isCustom: true }); + }) + .catch(err => { + logger.error(`Error during widget ${widgetId} installation: ${err}`); + deleteWidget(widgetId); + ArchiveHelper.cleanTempData(widgetTempPath); + throw err; + }); + }); +} + +export function updateWidget(updateWidgetId, archiveUrl, req) { + logger.debug('Updating widget', updateWidgetId, 'from', archiveUrl || 'file'); + + return ArchiveHelper.removeOldExtracts(widgetTempDir) + .then(() => (archiveUrl ? saveDataFromUrl(archiveUrl) : saveMultipartData(req))) + .then(data => { + const widgetTempPath = data.archiveFolder; + const widgetZipFile = data.archiveFile; + const widgetId = pathlib.parse(pathlib.parse(widgetZipFile).name).name; + const archivePath = pathlib.join(widgetTempPath, widgetZipFile); + const extractedDir = pathlib.join(widgetTempPath, widgetId); + + return backupWidget(updateWidgetId, widgetTempPath) + .then(() => validateConsistency(updateWidgetId, widgetId)) + .then(() => ArchiveHelper.decompressArchive(archivePath, extractedDir)) + .then(() => validateWidget(widgetId, extractedDir)) + .then(tempPath => installFiles(widgetId, tempPath)) + .then(() => BackendHandler.removeWidgetBackend(widgetId)) + .then(() => BackendHandler.importWidgetBackend(widgetId)) + .then(() => { + const widgetPath = pathlib.resolve(userWidgetsFolder, widgetId); + + logger.info('Widget updated'); + logger.info('Widget id:', widgetId); + logger.info('Widget path:', pathlib.resolve(widgetPath)); + + ArchiveHelper.cleanTempData(widgetTempPath); + + return Promise.resolve({ id: widgetId, isCustom: true }); + }) + .catch(err => { + logger.error(`Error during widget ${widgetId} update: ${err}`); + restoreBackup(updateWidgetId, widgetTempPath) + .then(() => BackendHandler.removeWidgetBackend(widgetId)) + .then(() => BackendHandler.importWidgetBackend(widgetId)) + .then(() => ArchiveHelper.cleanTempData(widgetTempPath)); + throw err; + }); + }); +} +export function listWidgets() { + const builtInWidgets = _.map(getBuiltInWidgets(), widget => ({ id: widget, isCustom: false })); + const userWidgets = _.map(getUserWidgets(), widget => ({ id: widget, isCustom: true })); + + return Promise.resolve(_.concat(builtInWidgets, userWidgets)); +} + +export function isWidgetUsed(widgetId) { + return db.UserApp.findAll({ attributes: ['appData', 'username'] }).then(userApp => { + const result = []; + _.forEach(userApp, row => { + const filter = _.filter(row.appData.pages, { widgets: [{ definition: widgetId }] }); + if (!_.isEmpty(filter)) { + // TODO(RD-1459) Refactor results to be just list of usernames + result.push({ username: row.username, managerIp: '' }); + } + }); + + return result; + }); +} + +export function init() { + return new Promise((resolve, reject) => { + try { + logger.info('Setting up user widgets directory:', userWidgetsFolder); + mkdirp.sync(userWidgetsFolder); + return BackendHandler.initWidgetBackends().then(resolve).catch(reject); + } catch (e) { + logger.error('Could not set up directory, error was:', e); + return reject(`Could not set up directory, error was: ${e}`); + } + }); +} diff --git a/backend/handler/__mocks__/ManagerHandler.ts b/backend/handler/__mocks__/ManagerHandler.ts new file mode 100644 index 0000000000..66f2fd34a2 --- /dev/null +++ b/backend/handler/__mocks__/ManagerHandler.ts @@ -0,0 +1,2 @@ +export const jsonRequest = jest.fn(); +export const updateOptions = jest.fn(); diff --git a/backend/handler/services/LoggerService.js b/backend/handler/services/LoggerService.js deleted file mode 100644 index bef31773a8..0000000000 --- a/backend/handler/services/LoggerService.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Created by jakubniezgoda on 06/11/2017. - */ - -const LoggerHandler = require('../LoggerHandler'); - -module.exports = (() => (category = '') => LoggerHandler.getLogger(`WidgetBackend${category ? `-${category}` : ''}`))(); diff --git a/backend/handler/services/LoggerService.ts b/backend/handler/services/LoggerService.ts new file mode 100644 index 0000000000..bf62d4c856 --- /dev/null +++ b/backend/handler/services/LoggerService.ts @@ -0,0 +1,3 @@ +import { getLogger } from '../LoggerHandler'; + +export default (category = '') => getLogger(`WidgetBackend${category ? `-${category}` : ''}`); diff --git a/backend/handler/services/ManagerService.js b/backend/handler/services/ManagerService.js deleted file mode 100644 index 614eddac49..0000000000 --- a/backend/handler/services/ManagerService.js +++ /dev/null @@ -1,67 +0,0 @@ -/* eslint no-underscore-dangle: ["error", { "allow": ["_size", "_offset"] }] */ - -const _ = require('lodash'); -const param = require('jquery-param'); -const ManagerHandler = require('../ManagerHandler'); -const consts = require('../../consts'); - -module.exports = (() => { - function call(method, url, { params, body = null, headers = {} } = {}) { - let fullUrl = url; - if (!_.isEmpty(params)) { - const queryString = (url.indexOf('?') > 0 ? '&' : '?') + param(params, true); - fullUrl = `${url}${queryString}`; - } - return ManagerHandler.jsonRequest(method, fullUrl, headers, body); - } - - function doGet(url, requestOptions) { - return call(consts.ALLOWED_METHODS_OBJECT.get, url, requestOptions); - } - - function doGetFull(url, requestOptions = {}, fullData = { items: [] }, size = 0) { - requestOptions.params = requestOptions.params || {}; - requestOptions.params._size = 1000; - requestOptions.params._offset = size; - - const promise = this.doGet(url, requestOptions); - - return promise.then(data => { - const cumulativeSize = size + data.items.length; - const totalSize = _.get(data, 'metadata.pagination.total'); - - fullData.items = _.concat(fullData.items, data.items); - - if (totalSize > cumulativeSize) { - return this.doGetFull(url, requestOptions, fullData, cumulativeSize); - } - return fullData; - }); - } - - function doPost(url, requestOptions) { - return call(consts.ALLOWED_METHODS_OBJECT.post, url, requestOptions); - } - - function doDelete(url, requestOptions) { - return call(consts.ALLOWED_METHODS_OBJECT.delete, url, requestOptions); - } - - function doPut(url, requestOptions) { - return call(consts.ALLOWED_METHODS_OBJECT.put, url, requestOptions); - } - - function doPatch(url, requestOptions) { - return call(consts.ALLOWED_METHODS_OBJECT.patch, url, requestOptions); - } - - return { - call, - doGet, - doGetFull, - doPost, - doDelete, - doPut, - doPatch - }; -})(); diff --git a/backend/handler/services/ManagerService.ts b/backend/handler/services/ManagerService.ts new file mode 100644 index 0000000000..16b60f0725 --- /dev/null +++ b/backend/handler/services/ManagerService.ts @@ -0,0 +1,56 @@ +// @ts-nocheck File not migrated fully to TS +/* eslint no-underscore-dangle: ["error", { "allow": ["_size", "_offset"] }] */ + +import _ from 'lodash'; +import param from 'jquery-param'; +import { jsonRequest } from '../ManagerHandler'; +import { ALLOWED_METHODS_OBJECT } from '../../consts'; + +export function call(method, url, { params, body = null, headers = {} } = {}) { + let fullUrl = url; + if (!_.isEmpty(params)) { + const queryString = (url.indexOf('?') > 0 ? '&' : '?') + param(params, true); + fullUrl = `${url}${queryString}`; + } + return jsonRequest(method, fullUrl, headers, body); +} + +export function doGet(url, requestOptions) { + return call(ALLOWED_METHODS_OBJECT.get, url, requestOptions); +} + +export function doGetFull(url, requestOptions = {}, fullData = { items: [] }, size = 0) { + requestOptions.params = requestOptions.params || {}; + requestOptions.params._size = 1000; + requestOptions.params._offset = size; + + const promise = doGet(url, requestOptions); + + return promise.then(data => { + const cumulativeSize = size + data.items.length; + const totalSize = _.get(data, 'metadata.pagination.total'); + + fullData.items = _.concat(fullData.items, data.items); + + if (totalSize > cumulativeSize) { + return doGetFull(url, requestOptions, fullData, cumulativeSize); + } + return fullData; + }); +} + +export function doPost(url, requestOptions) { + return call(ALLOWED_METHODS_OBJECT.post, url, requestOptions); +} + +export function doDelete(url, requestOptions) { + return call(ALLOWED_METHODS_OBJECT.delete, url, requestOptions); +} + +export function doPut(url, requestOptions) { + return call(ALLOWED_METHODS_OBJECT.put, url, requestOptions); +} + +export function doPatch(url, requestOptions) { + return call(ALLOWED_METHODS_OBJECT.patch, url, requestOptions); +} diff --git a/backend/handler/services/RequestService.js b/backend/handler/services/RequestService.js deleted file mode 100644 index 4813784853..0000000000 --- a/backend/handler/services/RequestService.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Created by jakubniezgoda on 06/11/2017. - */ - -const _ = require('lodash'); -const param = require('jquery-param'); -const RequestHandler = require('../RequestHandler'); -const consts = require('../../consts'); - -module.exports = (() => { - function call(method, url, { params, body, parseResponse = true, headers, certificate } = {}) { - return new Promise((resolve, reject) => { - const options = { headers: {} }; - let fullUrl = url; - if (!_.isEmpty(params)) { - const queryString = (url.indexOf('?') > 0 ? '&' : '?') + param(params, true); - fullUrl = `${url}${queryString}`; - } - if (headers) { - options.headers = _.omit(headers, 'cert'); - } - if (certificate) { - options.agentOptions = { - ca: certificate - }; - } - if (body) { - options.json = body; - try { - const strData = JSON.stringify(body); - options.headers['content-length'] = Buffer.byteLength(strData); - } catch (error) { - throw new Error(`Invalid (non-json) payload data. Error: ${error}`); - } - } - - RequestHandler.request( - method, - fullUrl, - options, - res => { - const isSuccess = res.statusCode >= 200 && res.statusCode < 300; - let responseBody = ''; - res.on('data', chunk => { - responseBody += chunk; - }); - res.on('end', () => { - if (isSuccess) { - if (parseResponse) { - const contentType = _.toLower(res.headers['content-type']); - if (contentType.indexOf('application/json') >= 0) { - try { - responseBody = JSON.parse(responseBody); - } catch (error) { - reject(`Invalid JSON response. Cannot parse. Data received: ${responseBody}`); - } - } - } - resolve(responseBody); - } else { - reject(`Status: ${res.statusCode} ${res.statusMessage}. Data received: ${responseBody}`); - } - }); - }, - err => { - reject(err); - } - ); - }); - } - - function doGet(url, requestOptions) { - return call(consts.ALLOWED_METHODS_OBJECT.get, url, requestOptions); - } - - function doPost(url, requestOptions) { - return call(consts.ALLOWED_METHODS_OBJECT.post, url, requestOptions); - } - - function doDelete(url, requestOptions) { - return call(consts.ALLOWED_METHODS_OBJECT.delete, url, requestOptions); - } - - function doPut(url, requestOptions) { - return call(consts.ALLOWED_METHODS_OBJECT.put, url, requestOptions); - } - - function doPatch(url, requestOptions) { - return call(consts.ALLOWED_METHODS_OBJECT.patch, url, requestOptions); - } - - return { - call, - doGet, - doPost, - doDelete, - doPut, - doPatch - }; -})(); diff --git a/backend/handler/services/RequestService.ts b/backend/handler/services/RequestService.ts new file mode 100644 index 0000000000..bc7c00cc0c --- /dev/null +++ b/backend/handler/services/RequestService.ts @@ -0,0 +1,86 @@ +// @ts-nocheck File not migrated fully to TS +import _ from 'lodash'; +import param from 'jquery-param'; +import { request } from '../RequestHandler'; +import { ALLOWED_METHODS_OBJECT } from '../../consts'; + +export function call(method, url, { params, body, parseResponse = true, headers, certificate } = {}) { + return new Promise((resolve, reject) => { + const options = { headers: {} }; + let fullUrl = url; + if (!_.isEmpty(params)) { + const queryString = (url.indexOf('?') > 0 ? '&' : '?') + param(params, true); + fullUrl = `${url}${queryString}`; + } + if (headers) { + options.headers = _.omit(headers, 'cert'); + } + if (certificate) { + options.agentOptions = { + ca: certificate + }; + } + if (body) { + options.json = body; + try { + const strData = JSON.stringify(body); + options.headers['content-length'] = Buffer.byteLength(strData); + } catch (error) { + throw new Error(`Invalid (non-json) payload data. Error: ${error}`); + } + } + + request( + method, + fullUrl, + options, + res => { + const isSuccess = res.statusCode >= 200 && res.statusCode < 300; + let responseBody = ''; + res.on('data', chunk => { + responseBody += chunk; + }); + res.on('end', () => { + if (isSuccess) { + if (parseResponse) { + const contentType = _.toLower(res.headers['content-type']); + if (contentType.indexOf('application/json') >= 0) { + try { + responseBody = JSON.parse(responseBody); + } catch (error) { + reject(`Invalid JSON response. Cannot parse. Data received: ${responseBody}`); + } + } + } + resolve(responseBody); + } else { + reject(`Status: ${res.statusCode} ${res.statusMessage}. Data received: ${responseBody}`); + } + }); + }, + err => { + reject(err); + } + ); + }); +} + +export function doGet(url, requestOptions) { + return call(ALLOWED_METHODS_OBJECT.get, url, requestOptions); +} + +export function doPost(url, requestOptions) { + return call(ALLOWED_METHODS_OBJECT.post, url, requestOptions); +} + +export function doDelete(url, requestOptions) { + return call(ALLOWED_METHODS_OBJECT.delete, url, requestOptions); +} + +export function doPut(url, requestOptions) { + return call(ALLOWED_METHODS_OBJECT.put, url, requestOptions); +} + +export function doPatch(url, requestOptions) { + return call(ALLOWED_METHODS_OBJECT.patch, url, requestOptions); +} diff --git a/backend/handler/services/index.js b/backend/handler/services/index.js deleted file mode 100644 index 03fde4b144..0000000000 --- a/backend/handler/services/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Created by jakubniezgoda on 06/11/2017. - */ - -const Manager = require('./ManagerService'); -const Logger = require('./LoggerService'); -const Request = require('./RequestService'); - -module.exports = { - Logger, - Manager, - Request -}; diff --git a/backend/handler/services/index.ts b/backend/handler/services/index.ts new file mode 100644 index 0000000000..0292b8b78f --- /dev/null +++ b/backend/handler/services/index.ts @@ -0,0 +1,5 @@ +import Logger from './LoggerService'; +import * as Manager from './ManagerService'; +import * as Request from './RequestService'; + +export { Manager, Logger, Request }; diff --git a/backend/jest.config.js b/backend/jest.config.js index 8c099b5c87..05c81c1e7a 100644 --- a/backend/jest.config.js +++ b/backend/jest.config.js @@ -6,7 +6,7 @@ let changedFiles; try { // Collect coverage only for files changed after this check was first introduced changedFiles = execSync( - 'git diff --name-only --relative 15dbf12734f1fc84ac92c6d8e2592ed96b8d3e9c -- "*.js" "**/*.js"' + 'git diff --name-only --relative 724e8a0f36f8d38cb461ab5373eb85af1f47a6dd -- "*.ts" "**/*.js" "**/*.ts"' ) .toString() .trim() @@ -18,9 +18,9 @@ try { module.exports = { collectCoverage: true, - collectCoverageFrom: [...changedFiles, 'config.js', '!migrations/**', '!jest.config.js', '!migration.js'], + collectCoverageFrom: [...changedFiles, 'config.ts', '!migrations/**', '!jest.config.js', '!migration.ts'], coverageThreshold: { - '**/*.js': { + '**/*.{js,ts}': { branches: 1, functions: 1, lines: 1, diff --git a/backend/migration.js b/backend/migration.js deleted file mode 100644 index 8a730d1964..0000000000 --- a/backend/migration.js +++ /dev/null @@ -1,5 +0,0 @@ -const { runMigration } = require('cloudify-ui-common/backend'); -const loggerFactory = require('./handler/LoggerHandler'); -const dbModule = require('./db/Connection'); - -runMigration(loggerFactory, dbModule); diff --git a/backend/migration.ts b/backend/migration.ts new file mode 100644 index 0000000000..4a38926847 --- /dev/null +++ b/backend/migration.ts @@ -0,0 +1,5 @@ +import { runMigration } from 'cloudify-ui-common/backend'; +import loggerFactory from './handler/LoggerHandler'; +import dbModule from './db/Connection'; + +runMigration(loggerFactory, dbModule); diff --git a/backend/migrations/20190401111659-5_0-MovePageResourcesToFiles.js b/backend/migrations/20190401111659-5_0-MovePageResourcesToFiles.js index 14dfaf98be..4734192dd3 100644 --- a/backend/migrations/20190401111659-5_0-MovePageResourcesToFiles.js +++ b/backend/migrations/20190401111659-5_0-MovePageResourcesToFiles.js @@ -4,7 +4,7 @@ const moment = require('moment'); const path = require('path'); const ResourceTypes = require('../db/types/ResourceTypes'); -const ResourcesModel = require('../db/ResourcesModel'); +const ResourcesModel = require('../db/models/ResourcesModel'); const Utils = require('../utils'); const userTemplatesFolder = Utils.getResourcePath('templates', true); diff --git a/backend/migrations/20190401130022-5_0-MoveTemplatesResourcesToFiles.js b/backend/migrations/20190401130022-5_0-MoveTemplatesResourcesToFiles.js index 7b3f175985..1d8adb2fc8 100644 --- a/backend/migrations/20190401130022-5_0-MoveTemplatesResourcesToFiles.js +++ b/backend/migrations/20190401130022-5_0-MoveTemplatesResourcesToFiles.js @@ -4,7 +4,7 @@ const moment = require('moment'); const path = require('path'); const ResourceTypes = require('../db/types/ResourceTypes'); -const ResourcesModel = require('../db/ResourcesModel'); +const ResourcesModel = require('../db/models/ResourcesModel'); const Utils = require('../utils'); const userTemplatesFolder = Utils.getResourcePath('templates', true); diff --git a/backend/migrations/20201030110539-5_1_1-MigratePageLayouts.js b/backend/migrations/20201030110539-5_1_1-MigratePageLayouts.js index afb5c2bf8e..60f7c1315e 100644 --- a/backend/migrations/20201030110539-5_1_1-MigratePageLayouts.js +++ b/backend/migrations/20201030110539-5_1_1-MigratePageLayouts.js @@ -8,7 +8,7 @@ const Utils = require('../utils'); const userTemplatesFolder = Utils.getResourcePath('templates', true); const userPagesFolder = path.resolve(userTemplatesFolder, 'pages'); -const UserApp = require('../db/UserAppModel'); +const UserApp = require('../db/models/UserAppModel'); function migrate(queryInterface, Sequelize, pageProcessor) { UserApp(queryInterface.sequelize, Sequelize) diff --git a/backend/migrations/20201230113319-5_1_1-UpdateManagerIp.js b/backend/migrations/20201230113319-5_1_1-UpdateManagerIp.js index b4740c2ee5..2e0ac8edd6 100644 --- a/backend/migrations/20201230113319-5_1_1-UpdateManagerIp.js +++ b/backend/migrations/20201230113319-5_1_1-UpdateManagerIp.js @@ -1,5 +1,5 @@ -const UserApp = require('../db/UserAppModel'); -const config = require('../config').get(); +const UserApp = require('../db/models/UserAppModel'); +const config = require('../config').getConfig(); module.exports = { up: (queryInterface, Sequelize) => { diff --git a/backend/package-lock.json b/backend/package-lock.json index e5dfe86c8f..b5628c08e9 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -79,6 +79,210 @@ } } }, + "@babel/helper-annotate-as-pure": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz", + "integrity": "sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", + "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", + "dev": true + }, + "@babel/types": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", + "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.9", + "to-fast-properties": "^2.0.0" + } + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.15.0.tgz", + "integrity": "sha512-MdmDXgvTIi4heDVX/e9EFfeGpugqm9fobBVg/iioE8kueXrOHdRDe36FAY7SnE9xXLVeYCoJR/gdrBEIHRC83Q==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-member-expression-to-functions": "^7.15.0", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/helper-replace-supers": "^7.15.0", + "@babel/helper-split-export-declaration": "^7.14.5" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.14.5" + } + }, + "@babel/generator": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.0.tgz", + "integrity": "sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==", + "dev": true, + "requires": { + "@babel/types": "^7.15.0", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", + "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", + "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.0.tgz", + "integrity": "sha512-Jq8H8U2kYiafuj2xMTPQwkTBnEEdGKpT35lJEQsRRjnG0LW3neucsaMWLgKcwu3OHKNeYugfw+Z20BXBSEs2Lg==", + "dev": true, + "requires": { + "@babel/types": "^7.15.0" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", + "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-replace-supers": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.0.tgz", + "integrity": "sha512-6O+eWrhx+HEra/uJnifCwhwMd6Bp5+ZfZeJwbqUTuqkhIT6YcRhiZCOOFChRypOIe0cV46kFrRBlm+t5vHCEaA==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.15.0", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.15.0", + "@babel/types": "^7.15.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", + "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", + "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", + "dev": true + }, + "@babel/highlight": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.15.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.3.tgz", + "integrity": "sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==", + "dev": true + }, + "@babel/template": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", + "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" + } + }, + "@babel/traverse": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.0.tgz", + "integrity": "sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.15.0", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.15.0", + "@babel/types": "^7.15.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", + "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.9", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, "@babel/helper-function-name": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", @@ -99,6 +303,33 @@ "@babel/types": "^7.10.4" } }, + "@babel/helper-hoist-variables": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", + "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", + "dev": true, + "requires": { + "@babel/types": "^7.14.5" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", + "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", + "dev": true + }, + "@babel/types": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", + "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.9", + "to-fast-properties": "^2.0.0" + } + } + } + }, "@babel/helper-member-expression-to-functions": { "version": "7.11.0", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", @@ -184,6 +415,12 @@ "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", "dev": true }, + "@babel/helper-validator-option": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "dev": true + }, "@babel/helpers": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", @@ -311,6 +548,61 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, + "@babel/plugin-syntax-typescript": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.14.5.tgz", + "integrity": "sha512-u6OXzDaIXjEstBRRoBCQ/uKQKlbuaeE5in0RvWdA4pN6AhqxTIwUsnHPU1CFZA/amYObMsuWhYfRl3Ch90HD0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", + "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", + "dev": true + } + } + }, + "@babel/plugin-transform-typescript": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.15.0.tgz", + "integrity": "sha512-WIIEazmngMEEHDaPTx0IZY48SaAmjVWe3TRSX7cmJXn0bEv9midFzAjxiruOWYIVf5iQ10vFx7ASDpgEO08L5w==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.15.0", + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/plugin-syntax-typescript": "^7.14.5" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", + "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", + "dev": true + } + } + }, + "@babel/preset-typescript": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.15.0.tgz", + "integrity": "sha512-lt0Y/8V3y06Wq/8H/u0WakrqciZ7Fz7mwPDHWUJAXlABL5hiUG42BNlRXiELNjeWjO5rWmnNKlx+yzJvxezHow==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5", + "@babel/helper-validator-option": "^7.14.5", + "@babel/plugin-transform-typescript": "^7.15.0" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", + "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", + "dev": true + } + } + }, "@babel/template": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", @@ -383,6 +675,19 @@ "minimist": "^1.2.0" } }, + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==" + }, + "@cspotcode/source-map-support": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.6.1.tgz", + "integrity": "sha512-DX3Z+T5dt1ockmPdobJS/FAsQPW4V4SrWEhD2iYQT2Cb2tQsiMnYxrcUH9By/Z3B+v0S5LMBkQtV/XOBbpLEOg==", + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, "@dabh/diagnostics": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", @@ -1013,6 +1318,26 @@ "@sinonjs/commons": "^1.7.0" } }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==" + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==" + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==" + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==" + }, "@types/babel__core": { "version": "7.1.9", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz", @@ -1054,12 +1379,63 @@ "@babel/types": "^7.3.0" } }, + "@types/body-parser": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", + "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/fs-extra": { + "version": "9.0.12", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.12.tgz", + "integrity": "sha512-I+bsBr67CurCGnSenZZ7v94gd3tc3+Aj2taxMT4yu4ABLuOgOjeFxX3dokG24ztSRg5tnT00sL8BszO7gSMoIw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/graceful-fs": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.3.tgz", @@ -1093,6 +1469,18 @@ "@types/istanbul-lib-report": "*" } }, + "@types/lodash": { + "version": "4.14.172", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.172.tgz", + "integrity": "sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw==", + "dev": true + }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, "@types/node": { "version": "12.20.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.1.tgz", @@ -1104,12 +1492,43 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, + "@types/passport": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.7.tgz", + "integrity": "sha512-JtswU8N3kxBYgo+n9of7C97YQBT+AYPP2aBfNGTzABqPAZnK/WOAaKfh3XesUYMZRrXFuoPc2Hv0/G/nQFveHw==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/prettier": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.0.tgz", "integrity": "sha512-hiYA88aHiEIgDmeKlsyVsuQdcFn3Z2VuFd/Xm/HCnGnPD8UFU5BM128uzzRVVGEzKDKYUrRsRH9S2o+NUy/3IA==", "dev": true }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", @@ -1290,6 +1709,11 @@ "readable-stream": "^2.0.0" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -2284,6 +2708,11 @@ "capture-stack-trace": "^1.0.0" } }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, "cross-env": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.0.tgz", @@ -2610,6 +3039,11 @@ } } }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" + }, "diff-sequences": { "version": "26.3.0", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.3.0.tgz", @@ -6134,6 +6568,11 @@ } } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, "makeerror": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", @@ -8885,6 +9324,37 @@ "utf8-byte-length": "^1.0.1" } }, + "ts-node": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.2.1.tgz", + "integrity": "sha512-hCnyOyuGmD5wHleOQX6NIjJtYVIO8bPP8F2acWkB4W06wdlkgyvJtubO/I9NkI88hCFECbsEgoLc0VNkYmcSfw==", + "requires": { + "@cspotcode/source-map-support": "0.6.1", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + }, + "dependencies": { + "acorn": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz", + "integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==" + }, + "acorn-walk": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.1.1.tgz", + "integrity": "sha512-FbJdceMlPHEAWJOILDk1fXD8lnTlEIWFkqtfk+MvmL5q/qlHfN7GEHcsFZWt/Tea9jRNPWUZG4G976nqAAmU9w==" + } + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -8948,6 +9418,11 @@ "is-typedarray": "^1.0.0" } }, + "typescript": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==" + }, "umzug": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/umzug/-/umzug-1.12.0.tgz", @@ -9589,6 +10064,11 @@ "fd-slicer": "~1.1.0" } }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" + }, "zip-stream": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.2.0.tgz", diff --git a/backend/package.json b/backend/package.json index 187dcc7b15..f00c68e821 100644 --- a/backend/package.json +++ b/backend/package.json @@ -5,24 +5,25 @@ "repository": "https://github.com/cloudify-cosmo/cloudify-stage/backend", "license": "Apache-2.0", "author": "Cloudify", - "main": "server.js", + "main": "server.ts", "scripts": { "audit": "npm audit --production", - "db-migrate": "node migration up", - "db-migrate-clear": "node migration clear", + "check-types": "tsc && tsc -b test", + "db-migrate": "ts-node -T migration up", + "db-migrate-clear": "ts-node -T migration clear", "db-migrate-create": "sequelize migration:create --config ../conf/app.json", - "db-migrate-current": "node migration current", - "db-migrate-down-to": "node migration downTo", - "db-migrate-reset": "node migration reset", - "db-migrate-status": "node migration status", - "startWithNodemon": "cross-env NODE_ENV=development NODE_TLS_REJECT_UNAUTHORIZED='0' nodemon --watch . --watch ../conf", - "devDebug": "npm run startWithNodemon -- --inspect server.js", - "devStart": "npm run startWithNodemon -- server.js", - "devTrace": "npm run startWithNodemon -- --trace-warnings server.js", + "db-migrate-current": "ts-node -T migration current", + "db-migrate-down-to": "ts-node -T migration downTo", + "db-migrate-reset": "ts-node -T migration reset", + "db-migrate-status": "ts-node -T migration status", + "startWithNodemon": "cross-env NODE_ENV=development NODE_TLS_REJECT_UNAUTHORIZED='0' nodemon -x 'node -r ts-node/register' -e js,ts,json --watch . --watch ../conf", + "devDebug": "npm run startWithNodemon -- --inspect server.ts", + "devStart": "npm run startWithNodemon -- server.ts", + "devTrace": "npm run startWithNodemon -- --trace-warnings server.ts", "postinstall": "patch-package", - "start": "node server.js ${STAGE_BACKEND_ARGS}", + "start": "ts-node -T server.ts ${STAGE_BACKEND_ARGS}", "test": "jest", - "wait-on-server": "wait-on tcp:localhost:8088 --timeout 5000 && echo 'Stage backend is up!'" + "wait-on-server": "wait-on tcp:localhost:8088 --timeout 10000 && echo 'Stage backend is up!'" }, "nodemonConfig": { "delay": 2000 @@ -59,11 +60,17 @@ "request": "^2.83.0", "sanitize-filename": "^1.6.1", "supertest": "^5.0.0", + "ts-node": "^10.2.1", + "typescript": "^4.3.5", "vm2": "^3.5.2", "wait-on": "^3.3.0" }, "devDependencies": { + "@babel/preset-typescript": "^7.15.0", + "@types/fs-extra": "^9.0.12", + "@types/lodash": "^4.14.172", "@types/node": "^12.20.1", + "@types/passport": "^1.0.7", "css": "^3.0.0", "jest": "^26.4.2", "nock": "^13.0.11", diff --git a/backend/routes/Applications.js b/backend/routes/Applications.ts similarity index 87% rename from backend/routes/Applications.js rename to backend/routes/Applications.ts index 5f3f218920..fe5fcb616d 100644 --- a/backend/routes/Applications.js +++ b/backend/routes/Applications.ts @@ -1,13 +1,11 @@ -/** - * Created by Alex on 21/03/2017. - */ +// @ts-nocheck File not migrated fully to TS -const bodyParser = require('body-parser'); -const express = require('express'); -const passport = require('passport'); +import bodyParser from 'body-parser'; +import express from 'express'; +import passport from 'passport'; +import { db } from '../db/Connection'; const router = express.Router(); -const { db } = require('../db/Connection'); router.use(passport.authenticate('token', { session: false })); router.use(bodyParser.json()); @@ -66,4 +64,4 @@ router.delete('/:id', (req, res, next) => { .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/routes/Auth.js b/backend/routes/Auth.ts similarity index 75% rename from backend/routes/Auth.js rename to backend/routes/Auth.ts index 902f530777..f6c407c449 100644 --- a/backend/routes/Auth.js +++ b/backend/routes/Auth.ts @@ -1,20 +1,17 @@ -/** - * Created by edenp on 7/30/17. - */ +// @ts-nocheck File not migrated fully to TS -const express = require('express'); -const bodyParser = require('body-parser'); -const passport = require('passport'); -const _ = require('lodash'); +import express from 'express'; +import bodyParser from 'body-parser'; +import passport from 'passport'; +import _ from 'lodash'; -const AuthHandler = require('../handler/AuthHandler'); -const Consts = require('../consts'); -const config = require('../config'); +import * as AuthHandler from '../handler/AuthHandler'; +import { CONTEXT_PATH, ROLE_COOKIE_NAME, TOKEN_COOKIE_NAME, USERNAME_COOKIE_NAME } from '../consts'; +import { getConfig } from '../config'; +import { getLogger } from '../handler/LoggerHandler'; const router = express.Router(); -const logger = require('../handler/LoggerHandler').getLogger('Auth'); - -const isSamlEnabled = _.get(config.get(), 'app.saml.enabled', false); +const logger = getLogger('Auth'); router.use(bodyParser.json()); router.use(bodyParser.urlencoded({ extended: true })); @@ -28,7 +25,7 @@ router.post('/login', (req, res) => AuthHandler.getToken(req.headers.authorization) .then(token => { const cookieOptions = getCookieOptions(req); - res.cookie(Consts.TOKEN_COOKIE_NAME, token.value, cookieOptions); + res.cookie(TOKEN_COOKIE_NAME, token.value, cookieOptions); res.send({ role: token.role }); }) .catch(err => { @@ -51,10 +48,10 @@ router.post('/saml/callback', passport.authenticate('saml', { session: false }), AuthHandler.getTokenViaSamlResponse(req.body.SAMLResponse) .then(token => { const cookieOptions = getCookieOptions(req); - res.cookie(Consts.TOKEN_COOKIE_NAME, token.value, cookieOptions); - res.cookie(Consts.USERNAME_COOKIE_NAME, req.user.username, cookieOptions); - res.cookie(Consts.ROLE_COOKIE_NAME, token.role, cookieOptions); - res.redirect(Consts.CONTEXT_PATH); + res.cookie(TOKEN_COOKIE_NAME, token.value, cookieOptions); + res.cookie(USERNAME_COOKIE_NAME, req.user.username, cookieOptions); + res.cookie(ROLE_COOKIE_NAME, token.role, cookieOptions); + res.redirect(CONTEXT_PATH); }) .catch(err => { logger.error(err); @@ -65,9 +62,10 @@ router.post('/saml/callback', passport.authenticate('saml', { session: false }), router.get('/manager', (req, res) => { const token = req.headers['authentication-token']; + const isSamlEnabled = _.get(getConfig(), 'app.saml.enabled', false); if (isSamlEnabled) { - res.clearCookie(Consts.USERNAME_COOKIE_NAME); - res.clearCookie(Consts.ROLE_COOKIE_NAME); + res.clearCookie(USERNAME_COOKIE_NAME); + res.clearCookie(ROLE_COOKIE_NAME); } Promise.all([AuthHandler.getManagerVersion(token), AuthHandler.getAndCacheConfig(token)]) .then(([version, rbac]) => @@ -100,7 +98,7 @@ router.get('/user', passport.authenticate('token', { session: false }), (req, re }); router.post('/logout', passport.authenticate('token', { session: false }), (req, res) => { - res.clearCookie(Consts.TOKEN_COOKIE_NAME); + res.clearCookie(TOKEN_COOKIE_NAME); res.end(); }); @@ -113,4 +111,4 @@ router.get('/RBAC', passport.authenticate('token', { session: false }), (req, re }); }); -module.exports = router; +export default router; diff --git a/backend/routes/BlueprintAdditions.js b/backend/routes/BlueprintAdditions.ts similarity index 86% rename from backend/routes/BlueprintAdditions.js rename to backend/routes/BlueprintAdditions.ts index e91fbed04f..79ca115024 100644 --- a/backend/routes/BlueprintAdditions.js +++ b/backend/routes/BlueprintAdditions.ts @@ -1,14 +1,12 @@ -/** - * Created by pposel on 09/03/2017. - */ -const express = require('express'); +// @ts-nocheck File not migrated fully to TS +import express from 'express'; +import bodyParser from 'body-parser'; +import path from 'path'; +import _ from 'lodash'; +import passport from 'passport'; +import { db } from '../db/Connection'; const router = express.Router(); -const bodyParser = require('body-parser'); -const path = require('path'); -const _ = require('lodash'); -const passport = require('passport'); -const { db } = require('../db/Connection'); router.use( bodyParser.raw({ @@ -62,4 +60,4 @@ router.delete('/image/:blueprint', passport.authenticate('token', { session: fal .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/routes/BlueprintUserData.js b/backend/routes/BlueprintUserData.ts similarity index 69% rename from backend/routes/BlueprintUserData.js rename to backend/routes/BlueprintUserData.ts index b82e1a7377..03c3c1131d 100644 --- a/backend/routes/BlueprintUserData.js +++ b/backend/routes/BlueprintUserData.ts @@ -1,11 +1,14 @@ -const yaml = require('js-yaml'); -const router = require('express').Router(); -const passport = require('passport'); -const bodyParser = require('body-parser'); -const _ = require('lodash'); +// @ts-nocheck File not migrated fully to TS +import yaml from 'js-yaml'; +import express from 'express'; +import passport from 'passport'; +import bodyParser from 'body-parser'; +import _ from 'lodash'; -const SourceHandler = require('../handler/SourceHandler'); -const { db } = require('../db/Connection'); +import { browseArchiveFile, browseArchiveTree } from '../handler/SourceHandler'; +import { db } from '../db/Connection'; + +const router = express.Router(); router.use(passport.authenticate('token', { session: false })); router.use(bodyParser.json()); @@ -15,14 +18,14 @@ router.get('/layout/:blueprintId', (req, res) => { if (blueprintData) { res.send(blueprintData.layout); } else { - SourceHandler.browseArchiveTree(req).then(data => { + browseArchiveTree(req).then(data => { const layoutFilePath = _.chain(data) .get('children[0].children') .find({ title: 'info.yaml' }) .get('key') .value(); if (layoutFilePath) { - SourceHandler.browseArchiveFile(req, data.timestamp, layoutFilePath) + browseArchiveFile(req, data.timestamp, layoutFilePath) .then(yaml.safeLoad) .then(layout => res.send(layout)); } else { @@ -40,4 +43,4 @@ router.put('/layout/:blueprint', (req, res) => { }).spread(blueprintData => blueprintData.update({ layout: req.body }).then(() => res.sendStatus(200))); }); -module.exports = router; +export default router; diff --git a/backend/routes/ClientConfig.js b/backend/routes/ClientConfig.ts similarity index 68% rename from backend/routes/ClientConfig.js rename to backend/routes/ClientConfig.ts index d832c9c987..cb86b4cd62 100644 --- a/backend/routes/ClientConfig.js +++ b/backend/routes/ClientConfig.ts @@ -1,14 +1,13 @@ -/** - * Created by kinneretzin on 08/03/2017. - */ +// @ts-nocheck File not migrated fully to TS + +import express from 'express'; +import bodyParser from 'body-parser'; +import passport from 'passport'; +import { db } from '../db/Connection'; -const express = require('express'); +import { getConfig } from '../config'; const router = express.Router(); -const bodyParser = require('body-parser'); -const passport = require('passport'); -const { db } = require('../db/Connection'); -const config = require('../config').get(); router.use(passport.authenticate('token', { session: false })); router.use(bodyParser.json()); @@ -18,7 +17,7 @@ router.use(bodyParser.json()); */ router.get('/', (req, res, next) => { db.ClientConfig.findOrCreate({ - where: { managerIp: config.manager.ip }, + where: { managerIp: getConfig().manager.ip }, defaults: { config: { canUserEdit: true } } }) .then(clientConfig => { @@ -29,7 +28,7 @@ router.get('/', (req, res, next) => { router.post('/', (req, res, next) => { db.ClientConfig.findOrCreate({ - where: { managerIp: config.manager.ip }, + where: { managerIp: getConfig().manager.ip }, defaults: { config: { canUserEdit: true } } }) .spread(clientConfig => { @@ -40,4 +39,4 @@ router.post('/', (req, res, next) => { .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/routes/External.js b/backend/routes/External.ts similarity index 71% rename from backend/routes/External.js rename to backend/routes/External.ts index 68ade47102..f54215d84b 100644 --- a/backend/routes/External.js +++ b/backend/routes/External.ts @@ -1,11 +1,10 @@ -/** - * Created by jakub.niezgoda on 25/06/2018. - */ -const express = require('express'); -const request = require('request'); +// @ts-nocheck File not migrated fully to TS +import express from 'express'; +import request from 'request'; +import { getLogger } from '../handler/LoggerHandler'; const router = express.Router(); -const logger = require('../handler/LoggerHandler').getLogger('External'); +const logger = getLogger('External'); function pipeRequest(req, res, next, url, queryString) { logger.debug(`Piping get request to url: ${url} with query string: ${queryString}`); @@ -22,4 +21,4 @@ router.get('/content', (req, res, next) => { pipeRequest(req, res, next, url, queryString); }); -module.exports = router; +export default router; diff --git a/backend/routes/File.js b/backend/routes/File.ts similarity index 82% rename from backend/routes/File.js rename to backend/routes/File.ts index 4be494d779..fe75fecf66 100644 --- a/backend/routes/File.js +++ b/backend/routes/File.ts @@ -1,16 +1,15 @@ -/** - * Created by jakubniezgoda on 24/11/2017. - */ +// @ts-nocheck File not migrated fully to TS -const express = require('express'); +import express from 'express'; +import passport from 'passport'; +import multer from 'multer'; +import yaml from 'js-yaml'; +import { getLogger } from '../handler/LoggerHandler'; const router = express.Router(); -const passport = require('passport'); -const multer = require('multer'); -const yaml = require('js-yaml'); const upload = multer({ limits: { fileSize: 50000 } }); -const logger = require('../handler/LoggerHandler').getLogger('File'); +const logger = getLogger('File'); function checkIfFileUploaded(req, res, next) { if (!req.file) { @@ -55,4 +54,4 @@ router.post( } ); -module.exports = router; +export default router; diff --git a/backend/routes/Filters.js b/backend/routes/Filters.ts similarity index 51% rename from backend/routes/Filters.js rename to backend/routes/Filters.ts index 1bd2241baf..ae6dd7d219 100644 --- a/backend/routes/Filters.js +++ b/backend/routes/Filters.ts @@ -1,15 +1,15 @@ -const express = require('express'); -const passport = require('passport'); -const FilterHandler = require('../handler/FilterHandler'); +import express from 'express'; +import passport from 'passport'; +import { getFilterUsage } from '../handler/FilterHandler'; const router = express.Router(); router.use(passport.authenticate('token', { session: false })); router.get('/usage/:filterId', (req, res, next) => { - FilterHandler.getFilterUsage(req.params.filterId) + getFilterUsage(req.params.filterId) .then(result => res.send(result)) .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/routes/GitHub.js b/backend/routes/GitHub.ts similarity index 87% rename from backend/routes/GitHub.js rename to backend/routes/GitHub.ts index 9f2693e47e..c03621f688 100644 --- a/backend/routes/GitHub.js +++ b/backend/routes/GitHub.ts @@ -1,17 +1,16 @@ -/** - * Created by pposel on 03/04/2017. - */ -const express = require('express'); -const request = require('request'); -const passport = require('passport'); -const _ = require('lodash'); -const config = require('../config'); -const ManagerHandler = require('../handler/ManagerHandler'); +// @ts-nocheck File not migrated fully to TS +import express from 'express'; +import request from 'request'; +import passport from 'passport'; +import _ from 'lodash'; +import { getConfig } from '../config'; +import { jsonRequest } from '../handler/ManagerHandler'; +import { getLogger } from '../handler/LoggerHandler'; const router = express.Router(); -const logger = require('../handler/LoggerHandler').getLogger('GitHub'); +const logger = getLogger('GitHub'); -const params = config.get().app.github; +const params = getConfig().app.github; const authList = {}; function getSecretName(secretName) { @@ -62,8 +61,8 @@ function setAuthorizationHeader(req, res, next, forceFetchCredentials) { const userSecret = getSecretName(params.username); const passSecret = getSecretName(params.password); Promise.all([ - ManagerHandler.jsonRequest('GET', `/secrets/${userSecret}`, req.headers), - ManagerHandler.jsonRequest('GET', `/secrets/${passSecret}`, req.headers) + jsonRequest('GET', `/secrets/${userSecret}`, req.headers), + jsonRequest('GET', `/secrets/${passSecret}`, req.headers) ]) .then(data => { const username = data[0]; @@ -134,4 +133,4 @@ router.get('/content/:user/:repo/master/:file', (req, res, next) => { ); }); -module.exports = router; +export default router; diff --git a/backend/routes/Maps.js b/backend/routes/Maps.ts similarity index 68% rename from backend/routes/Maps.js rename to backend/routes/Maps.ts index 3fb842024f..c559f05d80 100644 --- a/backend/routes/Maps.js +++ b/backend/routes/Maps.ts @@ -1,16 +1,19 @@ -const _ = require('lodash'); -const express = require('express'); -const passport = require('passport'); -const request = require('request'); +// @ts-nocheck File not migrated fully to TS +import _ from 'lodash'; +import express from 'express'; +import passport from 'passport'; +import request from 'request'; -const config = require('../config').get(); -const logger = require('../handler/LoggerHandler').getLogger('Maps'); -const ServerSettings = require('../serverSettings'); +import { getConfig } from '../config'; +import { getLogger } from '../handler/LoggerHandler'; +import { getMode, MODE_COMMUNITY } from '../serverSettings'; + +const logger = getLogger('Maps'); const router = express.Router(); function validateEdition(req, res, next) { - if (ServerSettings.settings.mode === ServerSettings.MODE_COMMUNITY) { + if (getMode() === MODE_COMMUNITY) { logger.error(`Endpoint ${req.baseUrl} not available in community edition.`); res.sendStatus(403); } @@ -22,7 +25,7 @@ router.use(validateEdition); router.get('/:z/:x/:y/:r?', (req, res) => { const { x, y, z, r = '' } = req.params; - const { accessToken, tilesUrlTemplate } = config.app.maps; + const { accessToken, tilesUrlTemplate } = getConfig().app.maps; const url = _.template(tilesUrlTemplate)({ x, y, z, r, accessToken }); logger.debug(`Fetching map tiles from ${tilesUrlTemplate}, x=${x}, y=${y}, z=${z}, r='${r}'.`); @@ -40,4 +43,4 @@ router.get('/:z/:x/:y/:r?', (req, res) => { ).pipe(res); }); -module.exports = router; +export default router; diff --git a/backend/routes/Plugins.js b/backend/routes/Plugins.ts similarity index 86% rename from backend/routes/Plugins.js rename to backend/routes/Plugins.ts index ad1b00446f..25a82df3c4 100644 --- a/backend/routes/Plugins.js +++ b/backend/routes/Plugins.ts @@ -1,20 +1,24 @@ -const express = require('express'); +// @ts-nocheck File not migrated fully to TS +import express from 'express'; +import passport from 'passport'; +import multer from 'multer'; +import yaml from 'js-yaml'; -const router = express.Router(); -const passport = require('passport'); -const multer = require('multer'); -const yaml = require('js-yaml'); +import _ from 'lodash'; -const upload = multer(); +import request from 'request'; +import archiver from 'archiver'; +import * as ManagerHandler from '../handler/ManagerHandler'; +import { getResponseJson } from '../handler/RequestHandler'; -const _ = require('lodash'); +import { getLogger } from '../handler/LoggerHandler'; -const request = require('request').defaults({ encoding: null }); -const archiver = require('archiver'); -const ManagerHandler = require('../handler/ManagerHandler'); -const RequestHandler = require('../handler/RequestHandler'); +const defaultedRequest = request.defaults({ encoding: null }); -const logger = require('../handler/LoggerHandler').getLogger('Plugins'); +const router = express.Router(); + +const upload = multer(); +const logger = getLogger('Plugins'); function checkParams(req, res, next) { const noWagon = req.files && _.isEmpty(req.files.wagon_file) && !req.query.wagonUrl; @@ -35,7 +39,7 @@ function checkParams(req, res, next) { function downloadFile(url) { return new Promise((resolve, reject) => { - request.get(url, (err, res, body) => { + defaultedRequest.get(url, (err, res, body) => { if (err) { logger.error(`Failed downloading ${url}. ${err}`); reject(err); @@ -74,7 +78,10 @@ router.get('/icons/:pluginId', (req, res) => { const options = {}; ManagerHandler.updateOptions(options, 'get'); req.pipe( - request(`${ManagerHandler.getManagerUrl()}/resources/plugins/${req.params.pluginId}/icon.png`, options).on( + defaultedRequest( + `${ManagerHandler.getManagerUrl()}/resources/plugins/${req.params.pluginId}/icon.png`, + options + ).on( 'response', // eslint-disable-next-line func-names function (response) { @@ -147,7 +154,7 @@ router.post( }, null, response => { - RequestHandler.getResponseJson(response) + getResponseJson(response) .then(data => { res.status(response.statusCode).send(data); }) @@ -170,4 +177,4 @@ router.post( } ); -module.exports = router; +export default router; diff --git a/backend/routes/ServerProxy.js b/backend/routes/ServerProxy.ts similarity index 69% rename from backend/routes/ServerProxy.js rename to backend/routes/ServerProxy.ts index 48d7d8db40..5fec1cb03c 100644 --- a/backend/routes/ServerProxy.js +++ b/backend/routes/ServerProxy.ts @@ -1,15 +1,14 @@ -/** - * Created by kinneretzin on 05/12/2016. - */ +// @ts-nocheck File not migrated fully to TS -const express = require('express'); -const request = require('request'); +import express from 'express'; +import request from 'request'; +import { isRbacInCache, getAndCacheConfig } from '../handler/AuthHandler'; +import { getApiUrl, updateOptions } from '../handler/ManagerHandler'; -const router = express.Router(); -const AuthHandler = require('../handler/AuthHandler'); -const ManagerHandler = require('../handler/ManagerHandler'); +import { getLogger } from '../handler/LoggerHandler'; -const logger = require('../handler/LoggerHandler').getLogger('ServerProxy'); +const router = express.Router(); +const logger = getLogger('ServerProxy'); function errorHandler(url, res, err) { const isTimeout = err.code === 'ETIMEDOUT'; @@ -38,7 +37,7 @@ function errorHandler(url, res, err) { function buildManagerUrl(req, res, next) { const serverUrl = req.originalUrl.substring(req.baseUrl.length); if (serverUrl) { - req.su = ManagerHandler.getApiUrl() + serverUrl; + req.su = getApiUrl() + serverUrl; logger.debug(`Proxying ${req.method} request to server with url: ${req.su}`); next(); } else { @@ -50,15 +49,11 @@ async function proxyRequest(req, res) { const options = {}; // if is a maintenance status fetch then update RBAC cache if empty - if ( - req.su === `${ManagerHandler.getApiUrl()}/maintenance` && - req.method === 'GET' && - !AuthHandler.isRbacInCache() - ) { - await AuthHandler.getAndCacheConfig(req.headers['authentication-token']); + if (req.su === `${getApiUrl()}/maintenance` && req.method === 'GET' && !isRbacInCache()) { + await getAndCacheConfig(req.headers['authentication-token']); } - ManagerHandler.updateOptions(options, req.method); + updateOptions(options, req.method); req.pipe( request(req.su, options).on('error', err => { @@ -72,4 +67,4 @@ async function proxyRequest(req, res) { */ router.all('/*', buildManagerUrl, proxyRequest); -module.exports = router; +export default router; diff --git a/backend/routes/SourceBrowser.js b/backend/routes/SourceBrowser.ts similarity index 67% rename from backend/routes/SourceBrowser.js rename to backend/routes/SourceBrowser.ts index 89d5ad6b1f..ddeb458139 100644 --- a/backend/routes/SourceBrowser.js +++ b/backend/routes/SourceBrowser.ts @@ -1,10 +1,8 @@ -/** - * Created by pposel on 24/02/2017. - */ +// @ts-nocheck File not migrated fully to TS -const express = require('express'); -const passport = require('passport'); -const SourceHandler = require('../handler/SourceHandler'); +import express from 'express'; +import passport from 'passport'; +import { browseArchiveFile, browseArchiveTree, listYamlFiles, getBlueprintResources } from '../handler/SourceHandler'; const router = express.Router(); @@ -17,28 +15,28 @@ router.get('/browse/:blueprintId/file/:timestamp/*', (req, res, next) => { if (!path) { next('no file path passed [path]'); } else { - SourceHandler.browseArchiveFile(req, timestamp, path) + browseArchiveFile(req, timestamp, path) .then(content => res.contentType('application/text').send(content)) .catch(next); } }); router.get('/browse/:blueprintId/archive', (req, res, next) => { - SourceHandler.browseArchiveTree(req) + browseArchiveTree(req) .then(data => res.send(data)) .catch(next); }); router.put('/list/yaml', (req, res, next) => { - SourceHandler.listYamlFiles(req) + listYamlFiles(req) .then(data => res.send(data)) .catch(next); }); router.put('/list/resources', (req, res, next) => { - SourceHandler.getBlueprintResources(req) + getBlueprintResources(req) .then(data => res.send(data)) .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/routes/Style.js b/backend/routes/Style.ts similarity index 65% rename from backend/routes/Style.js rename to backend/routes/Style.ts index 688f4f999d..eeb9efff2c 100644 --- a/backend/routes/Style.js +++ b/backend/routes/Style.ts @@ -1,23 +1,23 @@ -/** - * Created by jakubniezgoda on 03/04/2017. - */ +// @ts-nocheck File not migrated fully to TS -const express = require('express'); -const path = require('path'); -const fs = require('fs'); -const ejs = require('ejs'); -const _ = require('lodash'); +import express from 'express'; +import path from 'path'; +import fs from 'fs'; +import ejs from 'ejs'; +import _ from 'lodash'; -const config = require('../config').get(); +import { getConfig } from '../config'; +import { getResourcePath } from '../utils'; + +import { getLogger } from '../handler/LoggerHandler'; const router = express.Router(); -const Utils = require('../utils'); -const logger = require('../handler/LoggerHandler').getLogger('Style'); +const logger = getLogger('Style'); const styleTemplateFile = path.resolve(__dirname, '../templates', 'style.ejs'); router.get('/', (req, res) => { - const { whiteLabel } = config.app; + const { whiteLabel } = getConfig().app; const stylesheetTemplate = fs.readFileSync(styleTemplateFile, 'utf8'); let stylesheet = ejs.render(stylesheetTemplate, { @@ -27,7 +27,7 @@ router.get('/', (req, res) => { }); if (!_.isEmpty(whiteLabel.customCssPath)) { - const customCssPath = Utils.getResourcePath(whiteLabel.customCssPath, true); + const customCssPath = getResourcePath(whiteLabel.customCssPath, true); try { const customCss = fs.readFileSync(customCssPath, 'utf8'); logger.log('Adding CSS content from', customCssPath); @@ -43,4 +43,4 @@ router.get('/', (req, res) => { res.send(stylesheet); }); -module.exports = router; +export default router; diff --git a/backend/routes/Templates.js b/backend/routes/Templates.ts similarity index 87% rename from backend/routes/Templates.js rename to backend/routes/Templates.ts index 177c4c39fb..0bbaab0939 100644 --- a/backend/routes/Templates.js +++ b/backend/routes/Templates.ts @@ -1,11 +1,9 @@ -/** - * Created by pposel on 10/04/2017. - */ +// @ts-nocheck File not migrated fully to TS -const express = require('express'); -const bodyParser = require('body-parser'); -const passport = require('passport'); -const TemplateHandler = require('../handler/TemplateHandler'); +import express from 'express'; +import bodyParser from 'body-parser'; +import passport from 'passport'; +import * as TemplateHandler from '../handler/TemplateHandler'; const router = express.Router(); @@ -72,4 +70,4 @@ router.get('/select', (req, res, next) => { .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/routes/UserApp.js b/backend/routes/UserApp.ts similarity index 78% rename from backend/routes/UserApp.js rename to backend/routes/UserApp.ts index bcafa681ed..75b1aac3e9 100644 --- a/backend/routes/UserApp.js +++ b/backend/routes/UserApp.ts @@ -1,14 +1,12 @@ -/** - * Created by kinneretzin on 13/02/2017. - */ -const express = require('express'); +// @ts-nocheck File not migrated fully to TS +import express from 'express'; +import bodyParser from 'body-parser'; +import passport from 'passport'; +import { db } from '../db/Connection'; -const router = express.Router(); -const bodyParser = require('body-parser'); -const passport = require('passport'); -const { db } = require('../db/Connection'); +import { getMode } from '../serverSettings'; -const ServerSettings = require('../serverSettings'); +const router = express.Router(); router.use(passport.authenticate('token', { session: false })); router.use(bodyParser.json()); @@ -20,7 +18,7 @@ router.get('/', (req, res, next) => { db.UserApp.findOne({ where: { username: req.user.username, - mode: ServerSettings.settings.mode, + mode: getMode(), tenant: req.headers.tenant } }) @@ -34,7 +32,7 @@ router.post('/', (req, res, next) => { db.UserApp.findOrCreate({ where: { username: req.user.username, - mode: ServerSettings.settings.mode, + mode: getMode(), tenant: req.headers.tenant }, defaults: { appData: {}, appDataVersion: req.body.version } @@ -54,7 +52,7 @@ router.get('/clear-pages', (req, res, next) => { db.UserApp.findOne({ where: { username: req.user.username, - mode: ServerSettings.settings.mode, + mode: getMode(), tenant: req.query.tenant } }) @@ -68,4 +66,4 @@ router.get('/clear-pages', (req, res, next) => { .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/routes/WidgetBackend.js b/backend/routes/WidgetBackend.js deleted file mode 100644 index 028c625705..0000000000 --- a/backend/routes/WidgetBackend.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Created by jakubniezgoda on 13/09/2017. - */ - -const express = require('express'); -const passport = require('passport'); - -const BackendHandler = require('../handler/BackendHandler'); -const logger = require('../handler/LoggerHandler').getLogger('WidgetBackend'); - -const router = express.Router(); - -router.use(passport.authenticate('token', { session: false })); - -router.use('/:service', (req, res, next) => { - logger.debug( - `${req.method} request on service '${req.params.service}' called with parameters: ${JSON.stringify(req.query)}` - ); - return BackendHandler.callService(req.params.service, req.method, req, res, next).catch(next); -}); - -module.exports = router; diff --git a/backend/routes/WidgetBackend.ts b/backend/routes/WidgetBackend.ts new file mode 100644 index 0000000000..0422e5f4d6 --- /dev/null +++ b/backend/routes/WidgetBackend.ts @@ -0,0 +1,23 @@ +// @ts-nocheck File not migrated fully to TS + +import express from 'express'; +import passport from 'passport'; + +import { callService } from '../handler/BackendHandler'; + +import { getLogger } from '../handler/LoggerHandler'; + +const logger = getLogger('WidgetBackend'); + +const router = express.Router(); + +router.use(passport.authenticate('token', { session: false })); + +router.use('/:service', (req, res, next) => { + logger.debug( + `${req.method} request on service '${req.params.service}' called with parameters: ${JSON.stringify(req.query)}` + ); + return callService(req.params.service, req.method, req, res, next).catch(next); +}); + +export default router; diff --git a/backend/routes/Widgets.js b/backend/routes/Widgets.ts similarity index 74% rename from backend/routes/Widgets.js rename to backend/routes/Widgets.ts index 5dba42f686..a3b59718eb 100644 --- a/backend/routes/Widgets.js +++ b/backend/routes/Widgets.ts @@ -1,12 +1,10 @@ -/** - * Created by pposel on 10/04/2017. - */ +// @ts-nocheck File not migrated fully to TS -const express = require('express'); -const bodyParser = require('body-parser'); -const passport = require('passport'); -const WidgetHandler = require('../handler/WidgetHandler'); -const AuthHandler = require('../handler/AuthHandler'); +import express from 'express'; +import bodyParser from 'body-parser'; +import passport from 'passport'; +import * as WidgetHandler from '../handler/WidgetHandler'; +import { getRBAC, isAuthorized } from '../handler/AuthHandler'; const router = express.Router(); @@ -21,9 +19,9 @@ router.get('/list', (req, res, next) => { async function validateInstallWidgetsPermission(req, res, next) { const permissionId = 'stage_install_widgets'; - const rbac = await AuthHandler.getRBAC(req.headers['authentication-token']); + const rbac = await getRBAC(req.headers['authentication-token']); const permission = rbac.permissions[permissionId]; - if (!AuthHandler.isAuthorized(req.user, permission)) { + if (!isAuthorized(req.user, permission)) { res.sendStatus(403); } next(); @@ -53,4 +51,4 @@ router.delete('/:widgetId', validateInstallWidgetsPermission, (req, res, next) = .catch(next); }); -module.exports = router; +export default router; diff --git a/backend/samlSetup.js b/backend/samlSetup.ts similarity index 85% rename from backend/samlSetup.js rename to backend/samlSetup.ts index 6b4a085f79..9f5e40472b 100644 --- a/backend/samlSetup.js +++ b/backend/samlSetup.ts @@ -1,8 +1,5 @@ -/** - * Created by edenp on 07/09/2017. - */ - -exports.validate = samlConfig => { +// @ts-nocheck File not migrated fully to TS +export default samlConfig => { if (!samlConfig.certPath) { throw new Error('SAML is enabled, yet certificate path was not configured. [saml.certPath]'); } diff --git a/backend/server.js b/backend/server.js deleted file mode 100644 index a85d0bdd27..0000000000 --- a/backend/server.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Created by kinneretzin on 05/12/2016. - */ - -const app = require('./app'); -const Consts = require('./consts'); -const DBConnection = require('./db/Connection'); -const WidgetHandler = require('./handler/WidgetHandler'); -const TemplateHandler = require('./handler/TemplateHandler'); -const LoggerHandler = require('./handler/LoggerHandler'); -const { isDevelopmentOrTest } = require('./utils'); - -const logger = LoggerHandler.getLogger('Server'); -const ServerSettings = require('./serverSettings'); - -ServerSettings.init(); - -module.exports = DBConnection.init() - .then(() => { - logger.info('DB connection initialized successfully.'); - return Promise.all([WidgetHandler.init(), TemplateHandler.init()]); - }) - .then(() => { - logger.info('Widgets and templates data initialized successfully.'); - return new Promise((resolve, reject) => { - const server = app.listen(Consts.SERVER_PORT, Consts.SERVER_HOST); - server.on('error', reject); - server.on('listening', () => { - logger.info(`Server started in mode ${ServerSettings.settings.mode}`); - if (isDevelopmentOrTest) { - logger.info('Server started for development'); - } - logger.info(`Stage runs on ${Consts.SERVER_HOST}:${Consts.SERVER_PORT}!`); - resolve(server); - }); - }); - }) - .catch(error => { - logger.error(`Server initialization failed. ${error}`); - // eslint-disable-next-line no-process-exit - process.exit(1); - }); diff --git a/backend/server.ts b/backend/server.ts new file mode 100644 index 0000000000..acfba7d279 --- /dev/null +++ b/backend/server.ts @@ -0,0 +1,40 @@ +// @ts-nocheck File not migrated fully to TS +import app from './app'; +import { SERVER_HOST, SERVER_PORT } from './consts'; +import DBConnection from './db/Connection'; +import * as WidgetHandler from './handler/WidgetHandler'; +import * as TemplateHandler from './handler/TemplateHandler'; +import { getLogger } from './handler/LoggerHandler'; +import { isDevelopmentOrTest } from './utils'; + +import { init, getMode } from './serverSettings'; + +const logger = getLogger('Server'); + +init(); + +export default DBConnection.init() + .then(() => { + logger.info('DB connection initialized successfully.'); + return Promise.all([WidgetHandler.init(), TemplateHandler.init()]); + }) + .then(() => { + logger.info('Widgets and templates data initialized successfully.'); + return new Promise((resolve, reject) => { + const server = app.listen(SERVER_PORT, SERVER_HOST); + server.on('error', reject); + server.on('listening', () => { + logger.info(`Server started in mode ${getMode()}`); + if (isDevelopmentOrTest) { + logger.info('Server started for development'); + } + logger.info(`Stage runs on ${SERVER_HOST}:${SERVER_PORT}!`); + resolve(server); + }); + }); + }) + .catch(error => { + logger.error(`Server initialization failed`, error); + // eslint-disable-next-line no-process-exit + process.exit(1); + }); diff --git a/backend/serverSettings.js b/backend/serverSettings.js deleted file mode 100644 index 4f72ed486b..0000000000 --- a/backend/serverSettings.js +++ /dev/null @@ -1,47 +0,0 @@ -/* eslint-disable no-console, no-process-exit */ -/** - * Created by kinneretzin on 25/01/2017. - */ - -const _ = require('lodash'); - -const ServerSettings = { - MODE_MAIN: 'main', - MODE_CUSTOMER: 'customer', - MODE_COMMUNITY: 'community', - - settings: {}, - - init() { - this.settings = { - mode: this.MODE_MAIN - }; - const modes = [this.MODE_MAIN, this.MODE_CUSTOMER, this.MODE_COMMUNITY]; - - const displayUsage = () => { - console.log(`Usage: server.js -mode [${_.join(modes, '|')}]`); - process.exit(0); - }; - - process.argv.forEach((val, index) => { - if (val.toLowerCase() === '-h') { - displayUsage(); - } - - if (val.toLowerCase() === '-mode') { - if (process.argv.length > index + 1) { - const mode = process.argv[index + 1].toLowerCase(); - if (_.includes(modes, mode)) { - this.settings.mode = mode; - } else { - displayUsage(); - } - } else { - displayUsage(); - } - } - }); - } -}; - -module.exports = ServerSettings; diff --git a/backend/serverSettings.ts b/backend/serverSettings.ts new file mode 100644 index 0000000000..38248498fb --- /dev/null +++ b/backend/serverSettings.ts @@ -0,0 +1,46 @@ +/* eslint-disable no-console, no-process-exit */ + +import _ from 'lodash'; + +export const MODE_MAIN = 'main'; +export const MODE_CUSTOMER = 'customer'; +export const MODE_COMMUNITY = 'community'; + +let serverMode: string; + +export function setMode(mode: string) { + serverMode = mode; +} + +export function getMode() { + return serverMode; +} + +export function init() { + serverMode = MODE_MAIN; + const modes = [MODE_MAIN, MODE_CUSTOMER, MODE_COMMUNITY]; + + const displayUsage = () => { + console.log(`Usage: server.js -mode [${_.join(modes, '|')}]`); + process.exit(0); + }; + + process.argv.forEach((val, index) => { + if (val.toLowerCase() === '-h') { + displayUsage(); + } + + if (val.toLowerCase() === '-mode') { + if (process.argv.length > index + 1) { + const mode = process.argv[index + 1].toLowerCase(); + if (_.includes(modes, mode)) { + serverMode = mode; + } else { + displayUsage(); + } + } else { + displayUsage(); + } + } + }); +} diff --git a/backend/test/.eslintrc.json b/backend/test/.eslintrc.json index 32578aefc3..f534624b34 100644 --- a/backend/test/.eslintrc.json +++ b/backend/test/.eslintrc.json @@ -5,17 +5,17 @@ "extends": [ "plugin:jest/recommended" ], - "settings": { - "import/resolver": { - "jest": { - "jestConfigFile": "jest.config.js" - } - } - }, "rules": { "global-require": "off", "no-console": "off", - "node/no-missing-require": "off" + "node/no-missing-import": ["error", {"tryExtensions": [".js", ".json", ".ts"], "resolvePaths": ["backend", ".."]}], + "import/no-unresolved": "off", + "import/no-extraneous-dependencies": [ + "error", + { + "devDependencies": true + } + ] }, "env": { "jest": true diff --git a/backend/test/app.test.ts b/backend/test/app.test.ts new file mode 100644 index 0000000000..fd96311690 --- /dev/null +++ b/backend/test/app.test.ts @@ -0,0 +1,22 @@ +import { getConfig } from 'config'; +import validateSamlConfig from 'samlSetup'; +import 'app'; + +jest.mock('handler/ManagerHandler'); +jest.mock('auth/SamlStrategy'); + +jest.mock('config', () => ({ + getConfig: () => { + const config = jest.requireActual('config').getConfig(); + config.app.saml.enabled = true; + return config; + } +})); + +jest.mock('samlSetup'); + +describe('App', () => { + it('should validate SAML config', () => { + expect(validateSamlConfig).toHaveBeenCalledWith(getConfig().app.saml); + }); +}); diff --git a/backend/test/config.test.js b/backend/test/config.test.ts similarity index 62% rename from backend/test/config.test.js rename to backend/test/config.test.ts index 30d5e6d9ca..7b203fc725 100644 --- a/backend/test/config.test.js +++ b/backend/test/config.test.ts @@ -1,14 +1,16 @@ +import { getConfig, loadMeJson } from 'config'; + describe('Config', () => { beforeEach(() => jest.resetModules()); it('should throw error', () => { jest.doMock('../../conf/me.json', () => { throw Error('Test error'); }); - expect(() => require('../config')).toThrow('Test error'); + expect(loadMeJson).toThrow('Test error'); }); it('should construct manager URL', () => { jest.doMock('../../conf/me.json', () => ({})); - const config = require('../config'); - expect(config.get('main').managerUrl).toBe('https://127.0.0.1:53333'); + loadMeJson(); + expect(getConfig('main').managerUrl).toBe('https://127.0.0.1:53333'); }); }); diff --git a/backend/test/handlers/ArchiveHelper.test.js b/backend/test/handlers/ArchiveHelper.test.js deleted file mode 100644 index dc9016ccc7..0000000000 --- a/backend/test/handlers/ArchiveHelper.test.js +++ /dev/null @@ -1,20 +0,0 @@ -const { saveDataFromUrl } = require('handler/ArchiveHelper'); -const RequestHandler = require('handler/RequestHandler'); - -jest.mock('handler/RequestHandler', () => ({ - request: jest.fn((method, url, headers, data, onSuccess, onError) => onError()) -})); - -describe('ArchiveHelper', () => { - it('fetches extenral data with correct headers', () => { - return saveDataFromUrl('').catch(() => - expect(RequestHandler.request).toHaveBeenCalledWith( - 'GET', - expect.any(String), - expect.objectContaining({ headers: { 'User-Agent': 'Node.js' } }), - expect.anything(), - expect.anything() - ) - ); - }); -}); diff --git a/backend/test/handlers/ArchiveHelper.test.ts b/backend/test/handlers/ArchiveHelper.test.ts new file mode 100644 index 0000000000..86af191cef --- /dev/null +++ b/backend/test/handlers/ArchiveHelper.test.ts @@ -0,0 +1,25 @@ +// @ts-nocheck File not migrated fully to TS +import { saveDataFromUrl } from 'handler/ArchiveHelper'; +import { request } from 'handler/RequestHandler'; + +jest.mock('handler/ManagerHandler'); + +jest.mock('handler/RequestHandler', () => ({ + request: jest.fn((method, url, headers, data, onSuccess, onError) => onError()) +})); + +describe('ArchiveHelper', () => { + it('fetches external data with correct headers', () => { + expect.assertions(1); + const url = 'http://wp'; + return saveDataFromUrl(url).catch(() => + expect(request).toHaveBeenCalledWith( + 'GET', + url, + expect.objectContaining({ options: { headers: { 'User-Agent': 'Node.js' } } }), + expect.anything(), + expect.anything() + ) + ); + }); +}); diff --git a/backend/test/handlers/SourceHandler.test.js b/backend/test/handlers/SourceHandler.test.js deleted file mode 100644 index 18cafb8ac1..0000000000 --- a/backend/test/handlers/SourceHandler.test.js +++ /dev/null @@ -1,26 +0,0 @@ -const fs = require('fs-extra'); -const archiveHelper = require('../../handler/ArchiveHelper'); - -jest.mock('../../handler/ArchiveHelper'); -archiveHelper.saveDataFromUrl.mockResolvedValue({ archiveFolder: '', archiveFile: '' }); -archiveHelper.removeOldExtracts.mockResolvedValue(); -archiveHelper.decompressArchive.mockResolvedValue(); - -jest.mock('fs-extra'); -fs.statSync.mockReturnValueOnce({ isSymbolicLink: () => false, isFile: () => false, isDirectory: () => true }); -fs.readdirSync.mockReturnValueOnce(['subdir']); -fs.statSync.mockReturnValueOnce({ isSymbolicLink: () => false, isFile: () => false, isDirectory: () => true }); -fs.readdirSync.mockReturnValueOnce(['fileNameSpecial?#Characters']); -fs.statSync.mockReturnValueOnce({ isSymbolicLink: () => false, isFile: () => true }); - -describe('SourceHandler', () => { - const sourceHandler = require('../../handler/SourceHandler'); - - it('generates archive tree', () => { - return sourceHandler - .browseArchiveTree({ params: {} }) - .then(archiveTree => - expect(archiveTree.children[0].children[0].key).toEqual('subdir/fileNameSpecial%3F%23Characters') - ); - }); -}); diff --git a/backend/test/handlers/SourceHandler.test.ts b/backend/test/handlers/SourceHandler.test.ts new file mode 100644 index 0000000000..666b24a1c2 --- /dev/null +++ b/backend/test/handlers/SourceHandler.test.ts @@ -0,0 +1,32 @@ +// @ts-nocheck File not migrated fully to TS +import { statSync, readdirSync } from 'fs-extra'; +import { saveDataFromUrl, removeOldExtracts, decompressArchive } from 'handler/ArchiveHelper'; +import { browseArchiveTree } from 'handler/SourceHandler'; + +jest.mock('handler/ArchiveHelper'); +(saveDataFromUrl).mockResolvedValue({ archiveFolder: '', archiveFile: '' }); +(removeOldExtracts).mockResolvedValue(); +(decompressArchive).mockResolvedValue(); + +jest.mock('fs-extra'); +(statSync).mockReturnValueOnce({ + isSymbolicLink: () => false, + isFile: () => false, + isDirectory: () => true +}); +(readdirSync).mockReturnValueOnce(['subdir']); +(statSync).mockReturnValueOnce({ + isSymbolicLink: () => false, + isFile: () => false, + isDirectory: () => true +}); +(readdirSync).mockReturnValueOnce(['fileNameSpecial?#Characters']); +(statSync).mockReturnValueOnce({ isSymbolicLink: () => false, isFile: () => true }); + +describe('SourceHandler', () => { + it('generates archive tree', () => { + return browseArchiveTree({ params: {} }).then(archiveTree => + expect(archiveTree.children[0].children[0].key).toEqual('subdir/fileNameSpecial%3F%23Characters') + ); + }); +}); diff --git a/backend/test/handlers/TemplateHandler.test.js b/backend/test/handlers/TemplateHandler.test.js deleted file mode 100644 index e9ece799de..0000000000 --- a/backend/test/handlers/TemplateHandler.test.js +++ /dev/null @@ -1,81 +0,0 @@ -const fs = require('fs'); - -jest.mock('fs'); -fs.readdirSync.mockReturnValue([]); - -const rbac = { - roles: [ - { - type: 'system_role', - name: 'sys_admin' - }, - { - type: 'tenant_role', - name: 'manager' - }, - { - type: 'tenant_role', - name: 'user' - }, - { - type: 'tenant_role', - name: 'operations' - }, - { - type: 'tenant_role', - name: 'viewer' - }, - { - type: 'system_role', - name: 'default' - } - ], - permissions: {} -}; - -describe('TemplateHandler', () => { - describe('allows to select built-in template', () => { - beforeEach(() => { - jest.resetModules(); - jest.doMock('handler/AuthHandler', () => ({ - getRBAC: () => Promise.resolve(rbac) - })); - }); - - it('in Premium version', async () => { - jest.doMock('serverSettings', () => ({ - MODE_MAIN: 'main', - settings: { - mode: 'main' - } - })); - const TemplateHandler = require('../../handler/TemplateHandler'); - - await expect( - TemplateHandler.selectTemplate('default', { sys_admin: ['G1'] }, {}, 'default_tenant', '') - ).resolves.toEqual('main-sys_admin'); - - await expect(TemplateHandler.selectTemplate('sys_admin', {}, {}, '', '')).resolves.toEqual( - 'main-sys_admin' - ); - - await expect(TemplateHandler.selectTemplate('default', {}, {}, 'default_tenant', '')).resolves.toEqual( - 'main-default' - ); - }); - - it('in Community version', async () => { - jest.doMock('serverSettings', () => ({ - MODE_MAIN: 'main', - settings: { - mode: 'community' - } - })); - const TemplateHandler = require('../../handler/TemplateHandler'); - - await expect(TemplateHandler.selectTemplate('default', {}, {}, 'default_tenant', '')).resolves.toEqual( - 'community' - ); - }); - }); -}); diff --git a/backend/test/handlers/TemplateHandler.test.ts b/backend/test/handlers/TemplateHandler.test.ts new file mode 100644 index 0000000000..e0b093e98d --- /dev/null +++ b/backend/test/handlers/TemplateHandler.test.ts @@ -0,0 +1,64 @@ +import { readdirSync } from 'fs-extra'; +import { getRBAC } from 'handler/AuthHandler'; +import { getMode } from 'serverSettings'; + +import { selectTemplate } from 'handler/TemplateHandler'; + +jest.mock('fs'); +(readdirSync).mockReturnValue([]); + +const rbac = { + roles: [ + { + type: 'system_role', + name: 'sys_admin' + }, + { + type: 'tenant_role', + name: 'manager' + }, + { + type: 'tenant_role', + name: 'user' + }, + { + type: 'tenant_role', + name: 'operations' + }, + { + type: 'tenant_role', + name: 'viewer' + }, + { + type: 'system_role', + name: 'default' + } + ], + permissions: {} +}; + +jest.mock('handler/AuthHandler'); +(getRBAC).mockResolvedValue(rbac); + +jest.mock('serverSettings'); + +describe('TemplateHandler', () => { + describe('allows to select built-in template', () => { + it('in Premium version', async () => { + (getMode).mockReturnValue('main'); + + await expect(selectTemplate('default', { sys_admin: ['G1'] }, {}, 'default_tenant', '')).resolves.toEqual( + 'main-sys_admin' + ); + + await expect(selectTemplate('sys_admin', {}, {}, '', '')).resolves.toEqual('main-sys_admin'); + + await expect(selectTemplate('default', {}, {}, 'default_tenant', '')).resolves.toEqual('main-default'); + }); + + it('in Community version', async () => { + (getMode).mockReturnValue('community'); + await expect(selectTemplate('default', {}, {}, 'default_tenant', '')).resolves.toEqual('community'); + }); + }); +}); diff --git a/backend/test/handlers/WidgetHandler.test.js b/backend/test/handlers/WidgetHandler.test.ts similarity index 89% rename from backend/test/handlers/WidgetHandler.test.js rename to backend/test/handlers/WidgetHandler.test.ts index 2e2358ba4f..2c601f350d 100644 --- a/backend/test/handlers/WidgetHandler.test.js +++ b/backend/test/handlers/WidgetHandler.test.ts @@ -1,12 +1,15 @@ -const mkdirp = require('mkdirp'); -const Utils = require('../../utils'); -const WidgetHandler = require('../../handler/WidgetHandler'); +// @ts-nocheck File not migrated fully to TS +import mkdirp from 'mkdirp'; +import { getResourcePath } from 'utils'; +import { listWidgets } from 'handler/WidgetHandler'; + +jest.mock('handler/ManagerHandler'); describe('WidgetHandler', () => { it('allows to list all widgets', async () => { - const userWidgetsFolder = Utils.getResourcePath('widgets', true); + const userWidgetsFolder = getResourcePath('widgets', true); mkdirp.sync(userWidgetsFolder); - await expect(WidgetHandler.listWidgets()).resolves.toEqual([ + await expect(listWidgets()).resolves.toEqual([ { id: 'agents', isCustom: false }, { id: 'blueprintActionButtons', isCustom: false }, { id: 'blueprintCatalog', isCustom: false }, diff --git a/backend/test/handlers/services/LoggerService.test.ts b/backend/test/handlers/services/LoggerService.test.ts new file mode 100644 index 0000000000..b7cfcf05dd --- /dev/null +++ b/backend/test/handlers/services/LoggerService.test.ts @@ -0,0 +1,14 @@ +import { getLogger } from 'handler/LoggerHandler'; +import Logger from 'handler/services/LoggerService'; + +jest.mock('handler/LoggerHandler'); +const loggerMock = {}; +(getLogger).mockReturnValue(loggerMock); + +describe('LoggerService', () => { + it('should return logger instance', () => { + const logger = Logger(); + expect(logger).toBe(loggerMock); + expect(getLogger).toHaveBeenCalledWith('WidgetBackend'); + }); +}); diff --git a/backend/test/handlers/services/ManagerService.test.js b/backend/test/handlers/services/ManagerService.test.js deleted file mode 100644 index 1c1527cf7b..0000000000 --- a/backend/test/handlers/services/ManagerService.test.js +++ /dev/null @@ -1,15 +0,0 @@ -const ManagerService = require('handler/services/ManagerService'); -const ManagerHandler = require('handler/ManagerHandler'); - -jest.mock('handler/ManagerHandler', () => ({ - jsonRequest: jest.fn(() => Promise.resolve({ items: [] })) -})); - -describe('ManagerService', () => { - it('fetches all pages when performing GET request', () => { - return ManagerService.doGetFull('').then(response => { - expect(ManagerHandler.jsonRequest).toHaveBeenCalledWith('GET', '?_size=1000&_offset=0', {}, null); - expect(response).toEqual({ items: [] }); - }); - }); -}); diff --git a/backend/test/handlers/services/ManagerService.test.ts b/backend/test/handlers/services/ManagerService.test.ts new file mode 100644 index 0000000000..dd82d013af --- /dev/null +++ b/backend/test/handlers/services/ManagerService.test.ts @@ -0,0 +1,16 @@ +// @ts-nocheck File not migrated fully to TS +import { doGetFull } from 'handler/services/ManagerService'; +import { jsonRequest } from 'handler/ManagerHandler'; + +jest.mock('handler/ManagerHandler', () => ({ + jsonRequest: jest.fn(() => Promise.resolve({ items: [] })) +})); + +describe('ManagerService', () => { + it('fetches all pages when performing GET request', () => { + return doGetFull('').then(response => { + expect(jsonRequest).toHaveBeenCalledWith('GET', '?_size=1000&_offset=0', {}, null); + expect(response).toEqual({ items: [] }); + }); + }); +}); diff --git a/backend/test/handlers/services/RequestService.test.js b/backend/test/handlers/services/RequestService.test.ts similarity index 68% rename from backend/test/handlers/services/RequestService.test.js rename to backend/test/handlers/services/RequestService.test.ts index 7db0d7d938..c683814f44 100644 --- a/backend/test/handlers/services/RequestService.test.js +++ b/backend/test/handlers/services/RequestService.test.ts @@ -1,5 +1,6 @@ -const RequestService = require('handler/services/RequestService'); -const RequestHandler = require('handler/RequestHandler'); +// @ts-nocheck File not migrated fully to TS +import { doGet } from 'handler/services/RequestService'; +import { request } from 'handler/RequestHandler'; jest.mock('handler/RequestHandler', () => ({ request: jest.fn((method, url, options, onSuccess) => @@ -9,8 +10,8 @@ jest.mock('handler/RequestHandler', () => ({ describe('RequestService', () => { it('performs GET request', () => { - return RequestService.doGet().then(response => { - expect(RequestHandler.request).toHaveBeenCalledWith( + return doGet().then(response => { + expect(request).toHaveBeenCalledWith( 'GET', undefined, { headers: {} }, diff --git a/backend/test/migration.test.js b/backend/test/migration.test.ts similarity index 68% rename from backend/test/migration.test.js rename to backend/test/migration.test.ts index 319d14400b..1f093e660b 100644 --- a/backend/test/migration.test.js +++ b/backend/test/migration.test.ts @@ -1,16 +1,21 @@ +// @ts-nocheck File not migrated fully to TS // eslint-disable-next-line security/detect-child-process -const { execSync } = require('child_process'); -const { mkdirSync, renameSync, rmdirSync } = require('fs'); +import { execSync } from 'child_process'; +import { mkdirSync, renameSync, rmdirSync } from 'fs-extra'; -const Utils = require('../utils'); -const config = require('../config').get(); +import { getConfig } from 'config'; +import { getResourcePath } from '../utils'; const latestMigration = '20210519093609-6_0-UserAppsManagerIpColumnRemoval.js'; -const userTemplatesFolder = Utils.getResourcePath('templates', true); +const userTemplatesFolder = getResourcePath('templates', true); const userTemplatesBackupFolder = `${userTemplatesFolder}-backup`; describe('Migration script', () => { - beforeAll(() => execSync('node migration.js up')); + function execMigration(command: string) { + return execSync(`ts-node migration ${command}`); + } + + beforeAll(() => execMigration('up')); // Backup user templates for tests to restore later beforeAll(() => { @@ -23,27 +28,29 @@ describe('Migration script', () => { renameSync(userTemplatesBackupFolder, userTemplatesFolder); }); - beforeEach(() => execSync('node migration.js clear')); + beforeEach(() => execMigration('clear')); it('prints latest revision for "current" argument', () => { - const result = execSync('node migration.js current').toString(); + const result = execMigration('current').toString(); expect(result).toEqual(`${latestMigration}\n`); }); // eslint-disable-next-line jest/expect-expect it('handles migration down to the same version with no error', () => { - execSync(`node migration.js downTo ${latestMigration}`); + execMigration(`downTo ${latestMigration}`); }); function testMigrationUp(snapshotVersion, initialMigration) { // eslint-disable-next-line jest/expect-expect it(`migrates from ${snapshotVersion} snapshot`, () => { try { - execSync(`node migration.js downTo ${initialMigration}`); + execMigration(`downTo ${initialMigration}`); execSync( - `psql ${config.app.db.url} -v ON_ERROR_STOP=1 --single-transaction -f test/snapshots/${snapshotVersion}.sql` + `psql ${ + getConfig().app.db.url + } -v ON_ERROR_STOP=1 --single-transaction -f test/snapshots/${snapshotVersion}.sql` ); - execSync('node migration.js up'); + execMigration('up'); } catch (e) { console.log(`Error when migrating from ${initialMigration} for ${snapshotVersion}`); console.log(e.stdout.toString(), e.stderr.toString()); diff --git a/backend/test/mocks/mockDb.js b/backend/test/mocks/mockDb.js deleted file mode 100644 index 8a00b30528..0000000000 --- a/backend/test/mocks/mockDb.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = function mockDb(db) { - jest.doMock('../../db/Connection', () => { - return { - db - }; - }); -}; diff --git a/backend/test/mocks/passport.js b/backend/test/mocks/passport.js deleted file mode 100644 index 5f2fe7be7d..0000000000 --- a/backend/test/mocks/passport.js +++ /dev/null @@ -1,10 +0,0 @@ -const passport = require('passport'); - -function authMock(req, res, next) { - req.user = { username: 'testuser' }; - next(); -} - -jest.mock('passport'); -passport.authenticate.mockReturnValue(authMock); -passport.initialize.mockReturnValue(authMock); diff --git a/backend/test/routes/Applications.test.js b/backend/test/routes/Applications.test.ts similarity index 77% rename from backend/test/routes/Applications.test.js rename to backend/test/routes/Applications.test.ts index 0db4564c75..45532d20ce 100644 --- a/backend/test/routes/Applications.test.js +++ b/backend/test/routes/Applications.test.ts @@ -1,6 +1,11 @@ -const request = require('supertest'); -const mockDb = require('../mocks/mockDb'); -require('../mocks/passport'); +// @ts-nocheck File not migrated fully to TS + +import request from 'supertest'; +import app from 'app'; +import { mockDb } from 'db/Connection'; + +jest.mock('handler/ManagerHandler'); +jest.mock('db/Connection'); describe('/applications endpoint', () => { it('allows to get all data about applications', () => { @@ -13,7 +18,6 @@ describe('/applications endpoint', () => { ]) } }); - const app = require('app'); return request(app) .get('/console/applications') diff --git a/backend/test/routes/Auth.test.js b/backend/test/routes/Auth.test.ts similarity index 62% rename from backend/test/routes/Auth.test.js rename to backend/test/routes/Auth.test.ts index afec5ffb2b..0bb73b5644 100644 --- a/backend/test/routes/Auth.test.js +++ b/backend/test/routes/Auth.test.ts @@ -1,5 +1,19 @@ -const _ = require('lodash'); -const request = require('supertest'); +// @ts-nocheck File not migrated fully to TS +import request from 'supertest'; +import app from 'app'; +import { + getToken, + getTokenViaSamlResponse, + isProductLicensed, + getManagerVersion, + getAndCacheConfig, + getLicense +} from 'handler/AuthHandler'; +import { getConfig } from 'config'; + +jest.mock('handler/AuthHandler'); +jest.mock('handler/ManagerHandler'); +jest.mock('config', () => ({ getConfig: jest.fn(jest.requireActual('config').getConfig) })); function mockSamlConfig() { const samlConfig = { @@ -12,25 +26,14 @@ function mockSamlConfig() { } } }; - jest.doMock('config', () => { - const originalConfig = jest.requireActual('config').get(); - return { - get: () => _.merge(originalConfig, samlConfig) - }; - }); + (getConfig).mockReturnValue(samlConfig); } describe('/auth endpoint', () => { describe('/login handles', () => { - beforeEach(() => { - jest.resetModules(); - }); - it('valid token', () => { - jest.doMock('handler/AuthHandler', () => ({ - getToken: () => Promise.resolve({ value: 'xyz', role: 'default' }) - })); - return request(require('app')) + (getToken).mockResolvedValue({ value: 'xyz', role: 'default' }); + return request(app) .post('/console/auth/login') .expect(200) .then(response => { @@ -39,10 +42,8 @@ describe('/auth endpoint', () => { }); it('manager maintenance mode', () => { - jest.doMock('handler/AuthHandler', () => ({ - getToken: () => Promise.reject({ error_code: 'maintenance_mode_active' }) - })); - return request(require('app')) + (getToken).mockRejectedValue({ error_code: 'maintenance_mode_active' }); + return request(app) .post('/console/auth/login') .expect(423) .then(response => { @@ -51,10 +52,8 @@ describe('/auth endpoint', () => { }); it('invalid credentials', () => { - jest.doMock('handler/AuthHandler', () => ({ - getToken: () => Promise.reject({ error_code: 'unauthorized_error' }) - })); - return request(require('app')) + (getToken).mockRejectedValue({ error_code: 'unauthorized_error' }); + return request(app) .post('/console/auth/login') .expect(401) .then(response => { @@ -63,10 +62,8 @@ describe('/auth endpoint', () => { }); it('unknown errors', () => { - jest.doMock('handler/AuthHandler', () => ({ - getToken: () => Promise.reject({ error_code: 'out_of_memory', message: 'No resources available' }) - })); - return request(require('app')) + (getToken).mockRejectedValue({ error_code: 'out_of_memory', message: 'No resources available' }); + return request(app) .post('/console/auth/login') .expect(500) .then(response => { @@ -78,26 +75,18 @@ describe('/auth endpoint', () => { }); describe('/manager handles', () => { - const mockAuthHandler = (edition, overrides = {}) => - jest.doMock('handler/AuthHandler', () => { - const originalAuthHandler = jest.requireActual('handler/AuthHandler'); - return { - isProductLicensed: originalAuthHandler.isProductLicensed, - getManagerVersion: () => Promise.resolve({ version: '5.1.1', edition }), - getAndCacheConfig: () => Promise.resolve({ roles: [], permissions: {} }), - getLicense: () => Promise.resolve({ items: [{ license_edition: 'Shiny' }] }), - ...overrides - }; - }); - - beforeEach(() => { - jest.resetModules(); - require('../mocks/passport'); - }); + const mockAuthHandler = (edition = {}) => { + const originalAuthHandler = jest.requireActual('handler/AuthHandler'); + + (isProductLicensed).mockImplementation(originalAuthHandler.isProductLicensed); + (getManagerVersion).mockResolvedValue({ version: '5.1.1', edition }); + (getAndCacheConfig).mockResolvedValue({ roles: [], permissions: {} }); + (getLicense).mockResolvedValue({ items: [{ license_edition: 'Shiny' }] }); + }; it('licensed version', () => { mockAuthHandler('premium'); - return request(require('app')) + return request(app) .get('/console/auth/manager') .then(response => { expect(response.body).toStrictEqual({ @@ -110,7 +99,7 @@ describe('/auth endpoint', () => { it('unlicensed version', () => { mockAuthHandler('community'); - return request(require('app')) + return request(app) .get('/console/auth/manager') .then(response => { expect(response.body).toStrictEqual({ @@ -122,10 +111,9 @@ describe('/auth endpoint', () => { }); it('error response', () => { - mockAuthHandler('premium', { - getManagerVersion: () => Promise.reject({ message: 'Cannot get manager version' }) - }); - return request(require('app')) + mockAuthHandler('premium'); + (getManagerVersion).mockRejectedValue({ message: 'Cannot get manager version' }); + return request(app) .get('/console/auth/manager') .then(response => { expect(response.body).toStrictEqual({ @@ -137,7 +125,7 @@ describe('/auth endpoint', () => { it('clearing cookies when SAML is enabled', () => { mockAuthHandler('premium'); mockSamlConfig(); - return request(require('app')) + return request(app) .get('/console/auth/manager') .then(response => { const { 'set-cookie': setCookie } = response.headers; @@ -150,25 +138,13 @@ describe('/auth endpoint', () => { }); describe('/saml/callback handles', () => { - const mockAuthHandler = ( - getTokenViaSamlResponse = () => Promise.resolve({ value: 'token-content', role: 'sys_admin' }) - ) => - jest.doMock('handler/AuthHandler', () => ({ - getTokenViaSamlResponse - })); - beforeAll(() => { mockSamlConfig(); }); - beforeEach(() => { - jest.resetModules(); - require('../mocks/passport'); - }); - it('valid SAML response', () => { - mockAuthHandler(); - return request(require('app')) + (getTokenViaSamlResponse).mockResolvedValue({ value: 'token-content', role: 'sys_admin' }); + return request(app) .post('/console/auth/saml/callback') .send({ SAMLResponse: 'xyz' }) .expect(302) @@ -184,8 +160,8 @@ describe('/auth endpoint', () => { }); it('invalid SAML response', () => { - mockAuthHandler(); - return request(require('app')) + (getTokenViaSamlResponse).mockResolvedValue({ value: 'token-content', role: 'sys_admin' }); + return request(app) .post('/console/auth/saml/callback') .send({}) .expect(401) @@ -195,8 +171,8 @@ describe('/auth endpoint', () => { }); it('invalid token', () => { - mockAuthHandler(() => Promise.reject({ message: 'Token request invalid' })); - return request(require('app')) + (getTokenViaSamlResponse).mockRejectedValue({ message: 'Token request invalid' }); + return request(app) .post('/console/auth/saml/callback') .send({ SAMLResponse: 'xyz' }) .expect(500) diff --git a/backend/test/routes/BlueprintAdditions.test.js b/backend/test/routes/BlueprintAdditions.test.ts similarity index 73% rename from backend/test/routes/BlueprintAdditions.test.js rename to backend/test/routes/BlueprintAdditions.test.ts index 632ecea1d6..d0d47ac3c4 100644 --- a/backend/test/routes/BlueprintAdditions.test.js +++ b/backend/test/routes/BlueprintAdditions.test.ts @@ -1,6 +1,10 @@ -const request = require('supertest'); -const mockDb = require('../mocks/mockDb'); -require('../mocks/passport'); +// @ts-nocheck File not migrated fully to TS +import request from 'supertest'; +import { mockDb } from 'db/Connection'; +import app from 'app'; + +jest.mock('handler/ManagerHandler'); +jest.mock('db/Connection'); describe('/ba endpoint', () => { it('allows to get blueprint image', () => { @@ -9,7 +13,6 @@ describe('/ba endpoint', () => { findOne: () => Promise.resolve({ blueprintId: 1, image: null, imageUrl: 'http://test.url/image1.png' }) } }); - const app = require('app'); return request(app) .get('/console/ba/image/1') diff --git a/backend/test/routes/BlueprintUserData.test.js b/backend/test/routes/BlueprintUserData.test.ts similarity index 70% rename from backend/test/routes/BlueprintUserData.test.js rename to backend/test/routes/BlueprintUserData.test.ts index 64596b66be..267e4c82ba 100644 --- a/backend/test/routes/BlueprintUserData.test.js +++ b/backend/test/routes/BlueprintUserData.test.ts @@ -1,6 +1,10 @@ -const request = require('supertest'); -const mockDb = require('../mocks/mockDb'); -require('../mocks/passport'); +// @ts-nocheck File not migrated fully to TS +import request from 'supertest'; +import app from 'app'; +import { mockDb } from 'db/Connection'; + +jest.mock('handler/ManagerHandler'); +jest.mock('db/Connection'); describe('/bud endpoint', () => { it('allows to get layout for a blueprint', () => { @@ -9,7 +13,6 @@ describe('/bud endpoint', () => { findOne: () => Promise.resolve({ blueprintId: 1, username: 'test', layout: {} }) } }); - const app = require('app'); return request(app) .get('/console/bud/layout/1') diff --git a/backend/test/routes/ClientConfig.test.js b/backend/test/routes/ClientConfig.test.ts similarity index 80% rename from backend/test/routes/ClientConfig.test.js rename to backend/test/routes/ClientConfig.test.ts index b843f05ef3..ad5391e66a 100644 --- a/backend/test/routes/ClientConfig.test.js +++ b/backend/test/routes/ClientConfig.test.ts @@ -1,6 +1,10 @@ -const request = require('supertest'); -const mockDb = require('../mocks/mockDb'); -require('../mocks/passport'); +// @ts-nocheck File not migrated fully to TS +import request from 'supertest'; +import app from 'app'; +import { mockDb } from 'db/Connection'; + +jest.mock('db/Connection'); +jest.mock('handler/ManagerHandler'); describe('/clientConfig endpoint', () => { it('allows to get client config', () => { @@ -15,7 +19,6 @@ describe('/clientConfig endpoint', () => { ]) } }); - const app = require('app'); return request(app) .get('/console/clientConfig') diff --git a/backend/test/routes/Config.test.js b/backend/test/routes/Config.test.ts similarity index 77% rename from backend/test/routes/Config.test.js rename to backend/test/routes/Config.test.ts index 3507bb273c..846c32b0f5 100644 --- a/backend/test/routes/Config.test.js +++ b/backend/test/routes/Config.test.ts @@ -1,8 +1,11 @@ -const _ = require('lodash'); -const request = require('supertest'); -const app = require('app'); -const appConfig = require('conf/config.json'); -const userConfig = require('conf/userConfig.json'); +// @ts-nocheck File not migrated fully to TS +import _ from 'lodash'; +import request from 'supertest'; +import app from 'app'; +import appConfig from '../../../conf/config.json'; +import userConfig from '../../../conf/userConfig.json'; + +jest.mock('handler/ManagerHandler'); describe('/config endpoint', () => { it('allows to get config', () => diff --git a/backend/test/routes/Filters.test.js b/backend/test/routes/Filters.test.ts similarity index 92% rename from backend/test/routes/Filters.test.js rename to backend/test/routes/Filters.test.ts index c542aea126..89e0e4e742 100644 --- a/backend/test/routes/Filters.test.js +++ b/backend/test/routes/Filters.test.ts @@ -1,6 +1,10 @@ -const request = require('supertest'); -const mockDb = require('../mocks/mockDb'); -require('../mocks/passport'); +// @ts-nocheck File not migrated fully to TS +import request from 'supertest'; +import app from 'app'; +import { mockDb } from 'db/Connection'; + +jest.mock('db/Connection'); +jest.mock('handler/ManagerHandler'); describe('/filters endpoint', () => { const filterId = 'filterId'; @@ -50,7 +54,6 @@ describe('/filters endpoint', () => { ]) } }); - const app = require('app'); it('returns filter usage data', () => { return request(app) diff --git a/backend/test/routes/GitHub.test.js b/backend/test/routes/GitHub.test.ts similarity index 77% rename from backend/test/routes/GitHub.test.js rename to backend/test/routes/GitHub.test.ts index ec937fdf20..5d605406c2 100644 --- a/backend/test/routes/GitHub.test.js +++ b/backend/test/routes/GitHub.test.ts @@ -1,5 +1,8 @@ -const request = require('supertest'); -const app = require('app'); +// @ts-nocheck File not migrated fully to TS +import request from 'supertest'; +import app from 'app'; + +jest.mock('handler/ManagerHandler'); describe('/github endpoint', () => { it('allows to GET file content from GitHub repository', () => diff --git a/backend/test/routes/Maps.test.js b/backend/test/routes/Maps.test.ts similarity index 89% rename from backend/test/routes/Maps.test.js rename to backend/test/routes/Maps.test.ts index 59e036dee0..b3306e1f1c 100644 --- a/backend/test/routes/Maps.test.js +++ b/backend/test/routes/Maps.test.ts @@ -1,8 +1,12 @@ -const request = require('supertest'); -require('../mocks/passport'); +// @ts-nocheck File not migrated fully to TS +import request from 'supertest'; +import app from 'app'; +import mockRequest from 'request'; + +jest.mock('handler/ManagerHandler'); jest.mock('request', () => { - const mockRequest = jest.fn(() => { + const mock = jest.fn(() => { /** * @type {import('request').Response} */ @@ -54,22 +58,19 @@ jest.mock('request', () => { return proxiedRequest; }); - mockRequest.defaults = () => mockRequest; + mock.defaults = () => mock; - return mockRequest; + return mock; }); describe('/maps endpoint', () => { it('forwards requests to stadia and removes the HSTS header', () => { - const app = require('app'); - return request(app) .get('/console/maps/0/0/0/?noCache=1611669199559') .then(response => { expect(response.status).toBe(200); expect(response.headers['strict-transport-security']).toBe(undefined); - const mockRequest = require('request'); expect(mockRequest).toHaveBeenCalledTimes(1); const proxiedUrl = mockRequest.mock.calls[0][0]; expect(proxiedUrl).toEqual(expect.stringContaining('stadiamaps.com')); diff --git a/backend/test/routes/ServerProxy.test.js b/backend/test/routes/ServerProxy.test.ts similarity index 94% rename from backend/test/routes/ServerProxy.test.js rename to backend/test/routes/ServerProxy.test.ts index 7dd2f49eba..8a29a2827e 100644 --- a/backend/test/routes/ServerProxy.test.js +++ b/backend/test/routes/ServerProxy.test.ts @@ -1,5 +1,9 @@ -const request = require('supertest'); -const nock = require('nock'); +// @ts-nocheck File not migrated fully to TS +import request from 'supertest'; +import nock from 'nock'; + +import app from 'app'; +import { updateOptions } from 'handler/ManagerHandler'; const mockApiUrl = 'https://raw.githubusercontent.com'; const mockTimeout = 1000; @@ -11,9 +15,6 @@ jest.mock('handler/ManagerHandler', () => ({ }) })); -const app = require('app'); -const { updateOptions } = require('handler/ManagerHandler'); - describe('/sp endpoint', () => { const blueprintsUrl = '/blueprints'; const proxyBlueprintsUrl = `/console/sp${blueprintsUrl}`; diff --git a/backend/test/routes/SourceBrowser.test.js b/backend/test/routes/SourceBrowser.test.js deleted file mode 100644 index 1cf62a52ef..0000000000 --- a/backend/test/routes/SourceBrowser.test.js +++ /dev/null @@ -1,39 +0,0 @@ -const request = require('supertest'); -const fs = require('fs-extra'); -const os = require('os'); -const path = require('path'); -const archiveHelper = require('../../handler/ArchiveHelper'); -require('../mocks/passport'); - -jest.mock('../../handler/ArchiveHelper'); -archiveHelper.saveDataFromUrl.mockResolvedValue({ archiveFolder: '', archiveFile: '' }); -archiveHelper.removeOldExtracts.mockResolvedValue(); -archiveHelper.decompressArchive.mockResolvedValue(); - -jest.mock('fs-extra'); -fs.existsSync.mockReturnValue(false); -fs.statSync.mockReturnValue({ isSymbolicLink: () => true }); -const fileContent = 'fileContent'; -fs.readFile.mockResolvedValue(fileContent); - -describe('/source endpoint', () => { - it('allows to get blueprint file', () => { - const app = require('app'); - return request(app) - .get('/console/source/browse/blueprintId/file/timestamp/path/to/file') - .then(response => { - const blueprintDir = path.join(os.tmpdir(), 'cloudifyBrowseSources', 'blueprintIdtimestamp'); - const absoluteFilePath = path.join(blueprintDir, 'extracted', 'path', 'to', 'file'); - expect(fs.existsSync).toHaveBeenCalledWith(absoluteFilePath); - expect(archiveHelper.removeOldExtracts).toHaveBeenCalled(); - expect(archiveHelper.saveDataFromUrl).toHaveBeenCalledWith( - '/blueprints/blueprintId/archive', - blueprintDir, - expect.anything() - ); - expect(archiveHelper.decompressArchive).toHaveBeenCalled(); - expect(fs.readFile).toHaveBeenCalledWith(absoluteFilePath, 'utf-8'); - expect(response.text).toEqual(fileContent); - }); - }); -}); diff --git a/backend/test/routes/SourceBrowser.test.ts b/backend/test/routes/SourceBrowser.test.ts new file mode 100644 index 0000000000..3f5ee66f44 --- /dev/null +++ b/backend/test/routes/SourceBrowser.test.ts @@ -0,0 +1,39 @@ +// @ts-nocheck File not migrated fully to TS +import request from 'supertest'; +import { existsSync, readFile, statSync } from 'fs-extra'; +import os from 'os'; +import path from 'path'; +import { decompressArchive, saveDataFromUrl, removeOldExtracts } from 'handler/ArchiveHelper'; +import app from 'app'; + +jest.mock('handler/ArchiveHelper'); +(saveDataFromUrl).mockResolvedValue({ archiveFolder: '', archiveFile: '' }); +(removeOldExtracts).mockResolvedValue(); +(decompressArchive).mockResolvedValue(); + +jest.mock('fs-extra'); +(existsSync).mockReturnValue(false); +(statSync).mockReturnValue({ isSymbolicLink: () => true }); +const fileContent = 'fileContent'; +(readFile).mockResolvedValue(fileContent); + +describe('/source endpoint', () => { + it('allows to get blueprint file', () => { + return request(app) + .get('/console/source/browse/blueprintId/file/timestamp/path/to/file') + .then(response => { + const blueprintDir = path.join(os.tmpdir(), 'cloudifyBrowseSources', 'blueprintIdtimestamp'); + const absoluteFilePath = path.join(blueprintDir, 'extracted', 'path', 'to', 'file'); + expect(existsSync).toHaveBeenCalledWith(absoluteFilePath); + expect(removeOldExtracts).toHaveBeenCalled(); + expect(saveDataFromUrl).toHaveBeenCalledWith( + '/blueprints/blueprintId/archive', + blueprintDir, + expect.anything() + ); + expect(decompressArchive).toHaveBeenCalled(); + expect(readFile).toHaveBeenCalledWith(absoluteFilePath, 'utf-8'); + expect(response.text).toEqual(fileContent); + }); + }); +}); diff --git a/backend/test/routes/Style.test.js b/backend/test/routes/Style.test.ts similarity index 56% rename from backend/test/routes/Style.test.js rename to backend/test/routes/Style.test.ts index a25dcaeccb..65de6debb5 100644 --- a/backend/test/routes/Style.test.js +++ b/backend/test/routes/Style.test.ts @@ -1,6 +1,9 @@ -const request = require('supertest'); -const app = require('app'); -const css = require('css'); +// @ts-nocheck File not migrated fully to TS +import request from 'supertest'; +import app from 'app'; +import css from 'css'; + +jest.mock('handler/ManagerHandler'); describe('/style endpoint', () => { it('allows to get style', () => diff --git a/backend/test/routes/Templates.test.js b/backend/test/routes/Templates.test.ts similarity index 57% rename from backend/test/routes/Templates.test.js rename to backend/test/routes/Templates.test.ts index 92c53efaa0..2d92c3b86e 100644 --- a/backend/test/routes/Templates.test.js +++ b/backend/test/routes/Templates.test.ts @@ -1,11 +1,11 @@ -const request = require('supertest'); -const fs = require('fs-extra'); -require('../mocks/passport'); +// @ts-nocheck File not migrated fully to TS +import request from 'supertest'; +import { writeJson } from 'fs-extra'; -jest.mock('fs-extra'); -fs.writeJson.mockReturnValue(Promise.resolve()); +import app from 'app'; -const app = require('app'); +jest.mock('fs-extra'); +(writeJson).mockReturnValue(Promise.resolve()); describe('/templates endpoint', () => { it('allows to create a page', () => { @@ -15,7 +15,7 @@ describe('/templates endpoint', () => { .send(pageData) .then(response => { expect(response.statusCode).toBe(200); - expect(fs.writeJson).toHaveBeenCalledWith(expect.any(String), expect.objectContaining(pageData), { + expect(writeJson).toHaveBeenCalledWith(expect.any(String), expect.objectContaining(pageData), { spaces: ' ' }); }); diff --git a/backend/test/routes/UserApp.test.js b/backend/test/routes/UserApp.test.ts similarity index 81% rename from backend/test/routes/UserApp.test.js rename to backend/test/routes/UserApp.test.ts index 2e7d0fd705..7268e617fe 100644 --- a/backend/test/routes/UserApp.test.js +++ b/backend/test/routes/UserApp.test.ts @@ -1,6 +1,10 @@ -const request = require('supertest'); -const mockDb = require('../mocks/mockDb'); -require('../mocks/passport'); +// @ts-nocheck File not migrated fully to TS +import request from 'supertest'; +import app from 'app'; +import { mockDb } from 'db/Connection'; + +jest.mock('db/Connection'); +jest.mock('handler/ManagerHandler'); describe('/ua endpoint', () => { it('allows to get user layout', () => { @@ -16,7 +20,6 @@ describe('/ua endpoint', () => { }) } }); - const app = require('app'); return request(app) .get('/console/ua') diff --git a/backend/test/server.test.js b/backend/test/server.test.js deleted file mode 100644 index 247371e49e..0000000000 --- a/backend/test/server.test.js +++ /dev/null @@ -1,19 +0,0 @@ -describe('Server', () => { - it('should start', async () => { - jest.mock('vm2', () => { - return { - NodeVM: jest.fn().mockImplementation(() => { - return { - run: () => Promise.resolve() - }; - }), - VMScript: () => {} - }; - }); - - // eslint-disable-next-line global-require - const server = await require('server'); - expect(server.listening).toBeTruthy(); - await server.close(); - }); -}); diff --git a/backend/test/server.test.ts b/backend/test/server.test.ts new file mode 100644 index 0000000000..5af820e820 --- /dev/null +++ b/backend/test/server.test.ts @@ -0,0 +1,23 @@ +import serverStart from 'server'; + +jest.mock('handler/ManagerHandler'); + +jest.mock('vm2', () => { + return { + NodeVM: jest.fn().mockImplementation(() => { + return { + run: () => Promise.resolve() + }; + }), + VMScript: () => {} + }; +}); + +describe('Server', () => { + it('should start', async () => { + // eslint-disable-next-line global-require + const server = await serverStart; + expect(server.listening).toBeTruthy(); + await server.close(); + }); +}); diff --git a/backend/test/tsconfig.json b/backend/test/tsconfig.json index eb9b89431f..b4d73de547 100644 --- a/backend/test/tsconfig.json +++ b/backend/test/tsconfig.json @@ -4,5 +4,5 @@ "types": ["jest"], "baseUrl": ".." }, - "references": [{ "path": "../" }] + "include": ["../**/*", "../../conf/*.json"] } diff --git a/backend/test/widgets/executions/backend.test.js b/backend/test/widgets/executions/backend.test.ts similarity index 94% rename from backend/test/widgets/executions/backend.test.js rename to backend/test/widgets/executions/backend.test.ts index d7751c095e..fd9dbfa8ec 100644 --- a/backend/test/widgets/executions/backend.test.js +++ b/backend/test/widgets/executions/backend.test.ts @@ -1,6 +1,7 @@ +// @ts-nocheck File not migrated fully to TS /* eslint-disable import/no-dynamic-require */ -const _ = require('lodash'); -const widgetBackend = require('widgets/executions/src/backend.js'); +import _ from 'lodash'; +import widgetBackend from '../../../../widgets/executions/src/backend'; function getDataFor(test) { const referencesDirectory = './references'; diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 89984b0b03..d6ce282373 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -1,5 +1,7 @@ { - "extends": "../tsconfig.base.json", - "exclude": ["node_modules", "test", "dist"], - "include": ["**/*", "../conf/**/*.json"] + "extends": "ts-node/node12/tsconfig.json", + "compilerOptions": { + "noEmit": true + }, + "exclude": ["**/__mocks__/*", "test"] } diff --git a/backend/utils.js b/backend/utils.js deleted file mode 100644 index 63683f08d7..0000000000 --- a/backend/utils.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Created by jakub.niezgoda on 13/07/2018. - */ - -const pathlib = require('path'); - -const Consts = require('./consts'); - -const isDevelopmentOrTest = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test'; - -module.exports = { - isDevelopmentOrTest, - - getResourcePath: (path, isUserData) => { - if (isDevelopmentOrTest) { - // TODO(RD-1402): use a temporary directory during tests - return pathlib.resolve(`..${isUserData ? Consts.USER_DATA_PATH : ''}/${path}`); - } - return pathlib.resolve(`../dist/${isUserData ? Consts.USER_DATA_PATH : Consts.APP_DATA_PATH}/${path}`); - }, - - getValuesWithPaths(obj, key, arr = []) { - let objects = []; - Object.keys(obj).forEach(i => { - if (typeof obj[i] === 'object') { - objects = objects.concat(this.getValuesWithPaths(obj[i], key, [...arr, i])); - } else if (i === key) { - objects.push({ [obj[i]]: arr }); - } - }); - return objects; - }, - - getParams(query) { - return query - ? (/^[?#]/.test(query) ? query.slice(1) : query).split('&').reduce((params, param) => { - const [key, value] = param.split('='); - params[key] = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : ''; - return params; - }, {}) - : {}; - } -}; diff --git a/backend/utils.ts b/backend/utils.ts new file mode 100644 index 0000000000..9e45cd3cb5 --- /dev/null +++ b/backend/utils.ts @@ -0,0 +1,35 @@ +// @ts-nocheck File not migrated fully to TS +import pathlib from 'path'; +import * as Consts from './consts'; + +export const isDevelopmentOrTest = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test'; + +export function getResourcePath(path, isUserData) { + if (isDevelopmentOrTest) { + // TODO(RD-1402): use a temporary directory during tests + return pathlib.resolve(`..${isUserData ? Consts.USER_DATA_PATH : ''}/${path}`); + } + return pathlib.resolve(`../dist/${isUserData ? Consts.USER_DATA_PATH : Consts.APP_DATA_PATH}/${path}`); +} + +export function getValuesWithPaths(obj, key, arr = []) { + let objects = []; + Object.keys(obj).forEach(i => { + if (typeof obj[i] === 'object') { + objects = objects.concat(this.getValuesWithPaths(obj[i], key, [...arr, i])); + } else if (i === key) { + objects.push({ [obj[i]]: arr }); + } + }); + return objects; +} + +export function getParams(query) { + return query + ? (/^[?#]/.test(query) ? query.slice(1) : query).split('&').reduce((params, param) => { + const [key, value] = param.split('='); + params[key] = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : ''; + return params; + }, {}) + : {}; +} diff --git a/devServer.js b/devServer.js deleted file mode 100644 index 225ba410d9..0000000000 --- a/devServer.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Created by kinneretzin on 29/08/2016. - */ - -const webpack = require('webpack'); -const WebpackDevServer = require('webpack-dev-server'); - -const webpackConfig = require('./webpack.config')({}, { mode: 'development' }); -const Consts = require('./backend/consts'); -const startWidgetBackendWatcher = require('./scripts/widgetBackendWatcher'); - -const host = 'localhost'; -const devServerPort = 4000; -const stageBackendPort = 8088; -const contextPath = Consts.CONTEXT_PATH; - -const proxyOptions = { - target: `http://${host}:${stageBackendPort}`, - secure: false -}; - -const options = { - publicPath: contextPath, - host, - inline: false, - historyApiFallback: { - index: `${contextPath}/static/index.html` - }, - proxy: { - [`${contextPath}/auth`]: proxyOptions, - [`${contextPath}/ba`]: proxyOptions, - [`${contextPath}/bud`]: proxyOptions, - [`${contextPath}/clientConfig`]: proxyOptions, - [`${contextPath}/config`]: proxyOptions, - [`${contextPath}/external`]: proxyOptions, - [`${contextPath}/file`]: proxyOptions, - [`${contextPath}/filters`]: proxyOptions, - [`${contextPath}/github`]: proxyOptions, - [`${contextPath}/maps`]: proxyOptions, - [`${contextPath}/plugins`]: proxyOptions, - [`${contextPath}/source`]: proxyOptions, - [`${contextPath}/sp`]: proxyOptions, - [`${contextPath}/style`]: proxyOptions, - [`${contextPath}/templates`]: proxyOptions, - [`${contextPath}/ua`]: proxyOptions, - [`${contextPath}/userData`]: proxyOptions, - [`${contextPath}/wb`]: proxyOptions, - [`${contextPath}/widgets`]: proxyOptions - }, - watchOptions: { - ignored: ['**/userData/**'] - } -}; - -WebpackDevServer.addDevServerEntrypoints(webpackConfig[0], options); -const compiler = webpack(webpackConfig); -const server = new WebpackDevServer(compiler, options); - -server.listen(devServerPort, host, err => { - if (err) { - console.log(err); - } else { - console.log(`Listening at http://${host}:${devServerPort}/`); - } -}); - -startWidgetBackendWatcher(); diff --git a/devServer.ts b/devServer.ts new file mode 100644 index 0000000000..61d72e6e1b --- /dev/null +++ b/devServer.ts @@ -0,0 +1,63 @@ +import webpack from 'webpack'; +import WebpackDevServer from 'webpack-dev-server'; + +import getWebpackConfig from './webpack.config'; +import startWidgetBackendWatcher from './scripts/widgetBackendWatcher'; + +import { CONTEXT_PATH, SERVER_HOST, SERVER_PORT } from './backend/consts'; + +const webpackConfig = getWebpackConfig({}, { mode: 'development' }); + +const devServerPort = 4000; + +const proxyOptions = { + target: `http://${SERVER_HOST}:${SERVER_PORT}`, + secure: false +}; + +const options = { + publicPath: CONTEXT_PATH, + host: SERVER_HOST, + inline: false, + historyApiFallback: { + index: `${CONTEXT_PATH}/static/index.html` + }, + proxy: { + [`${CONTEXT_PATH}/auth`]: proxyOptions, + [`${CONTEXT_PATH}/ba`]: proxyOptions, + [`${CONTEXT_PATH}/bud`]: proxyOptions, + [`${CONTEXT_PATH}/clientConfig`]: proxyOptions, + [`${CONTEXT_PATH}/config`]: proxyOptions, + [`${CONTEXT_PATH}/external`]: proxyOptions, + [`${CONTEXT_PATH}/file`]: proxyOptions, + [`${CONTEXT_PATH}/filters`]: proxyOptions, + [`${CONTEXT_PATH}/github`]: proxyOptions, + [`${CONTEXT_PATH}/maps`]: proxyOptions, + [`${CONTEXT_PATH}/plugins`]: proxyOptions, + [`${CONTEXT_PATH}/source`]: proxyOptions, + [`${CONTEXT_PATH}/sp`]: proxyOptions, + [`${CONTEXT_PATH}/style`]: proxyOptions, + [`${CONTEXT_PATH}/templates`]: proxyOptions, + [`${CONTEXT_PATH}/ua`]: proxyOptions, + [`${CONTEXT_PATH}/userData`]: proxyOptions, + [`${CONTEXT_PATH}/wb`]: proxyOptions, + [`${CONTEXT_PATH}/widgets`]: proxyOptions + }, + watchOptions: { + ignored: ['**/userData/**'] + } +}; + +WebpackDevServer.addDevServerEntrypoints(webpackConfig[0], options); +const compiler = webpack(webpackConfig); +const server = new WebpackDevServer(compiler, options); + +server.listen(devServerPort, SERVER_HOST, err => { + if (err) { + console.log(err); + } else { + console.log(`Listening at http://${SERVER_HOST}:${devServerPort}/`); + } +}); + +startWidgetBackendWatcher(); diff --git a/package-lock.json b/package-lock.json index 66aa7cdd85..0c6ede684f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5262,6 +5262,21 @@ } } }, + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "optional": true + }, + "@cspotcode/source-map-support": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.6.1.tgz", + "integrity": "sha512-DX3Z+T5dt1ockmPdobJS/FAsQPW4V4SrWEhD2iYQT2Cb2tQsiMnYxrcUH9By/Z3B+v0S5LMBkQtV/XOBbpLEOg==", + "optional": true, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, "@cypress/browserify-preprocessor": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@cypress/browserify-preprocessor/-/browserify-preprocessor-3.0.1.tgz", @@ -7037,6 +7052,30 @@ } } }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "optional": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "optional": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "optional": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "optional": true + }, "@types/anymatch": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", @@ -8622,6 +8661,12 @@ "readable-stream": "^2.0.6" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "optional": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -12246,6 +12291,12 @@ "sha.js": "^2.4.8" } }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "optional": true + }, "cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -22770,6 +22821,12 @@ "pify": "^3.0.0" } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "optional": true + }, "makeerror": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", @@ -29403,6 +29460,46 @@ "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-2.0.12.tgz", "integrity": "sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w==" }, + "ts-node": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.2.1.tgz", + "integrity": "sha512-hCnyOyuGmD5wHleOQX6NIjJtYVIO8bPP8F2acWkB4W06wdlkgyvJtubO/I9NkI88hCFECbsEgoLc0VNkYmcSfw==", + "optional": true, + "requires": { + "@cspotcode/source-map-support": "0.6.1", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + }, + "dependencies": { + "acorn": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz", + "integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==", + "optional": true + }, + "acorn-walk": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.1.1.tgz", + "integrity": "sha512-FbJdceMlPHEAWJOILDk1fXD8lnTlEIWFkqtfk+MvmL5q/qlHfN7GEHcsFZWt/Tea9jRNPWUZG4G976nqAAmU9w==", + "optional": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "optional": true + } + } + }, "tsconfig-paths": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", @@ -31507,6 +31604,12 @@ } } }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "optional": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 3a74a95c91..3fcd376d60 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,11 @@ "build:coverage": "cross-env COVERAGE_CHECK=1 npm run build", "ci:frontend": "npm ci --prefer-offline", "ci:backend": "npm ci --prefix backend --prefer-offline", - "check-types": "tsc --build", + "check-types": "npm run check-types:frontend && npm run check-types:backend", + "check-types:frontend": "tsc --build", + "check-types:backend": "npm run --prefix backend check-types", "coverageCheck": "cp coverage-jest/coverage-final.json .nyc_output && nyc report --reporter lcov --report-dir coverage-overall && nyc check-coverage", - "devServer": "cross-env NODE_ENV=development node devServer.js", + "devServer": "cross-env NODE_ENV=development ts-node devServer.ts", "docCheck": "scripts/checkDocs.sh", "docWidgets": "node scripts/updateReadmes.js", "e2e": "cypress run -C node_modules/cloudify-ui-common/cypress/cypress.json", @@ -194,6 +196,7 @@ "size-limit": "^4.5.5", "source-map-loader": "^1.1.3", "timekeeper": "^1.0.0", + "ts-node": "^10.2.1", "webpack-dev-middleware": "^3.7.2", "webpack-dev-server": "^3.11.0" }, diff --git a/tsconfig.json b/tsconfig.json index 4f762e570d..e41053eef0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,12 @@ { + "compilerOptions": { + "noEmit": true, + "esModuleInterop": true + }, + "include": ["*.ts"], "references": [ { "path": "./tsconfig.ui.json" }, - { "path": "backend" }, - { "path": "backend/test" }, { "path": "test/jest" }, { "path": "test/cypress" } - ], - "files": [] + ] } diff --git a/tsconfig.ui.json b/tsconfig.ui.json index 6168e6bfdc..f3a3300a4b 100644 --- a/tsconfig.ui.json +++ b/tsconfig.ui.json @@ -1,4 +1,7 @@ { + "compilerOptions": { + "composite": true + }, "references": [{ "path": "app" }, { "path": "widgets" }], "files": [] } diff --git a/webpack.config.js b/webpack.config.js index 7a9687c501..1d16ce92e6 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -11,7 +11,7 @@ const ImageminPlugin = require('imagemin-webpack-plugin').default; const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); -const Consts = require('./backend/consts'); +const CONTEXT_PATH = '/console'; module.exports = (env, argv) => { const isProduction = argv.mode === 'production'; @@ -169,7 +169,7 @@ module.exports = (env, argv) => { output: { path: outputPath, filename: 'static/js/[name].bundle.js', - publicPath: Consts.CONTEXT_PATH + publicPath: CONTEXT_PATH }, module, plugins: _.flatten( @@ -238,7 +238,7 @@ module.exports = (env, argv) => { output: { path: path.join(outputPath, 'appData'), filename: 'widgets/[name]', - publicPath: Consts.CONTEXT_PATH + publicPath: CONTEXT_PATH }, module, plugins: _.flatten( @@ -272,7 +272,7 @@ module.exports = (env, argv) => { output: { path: path.join(outputPath, 'appData/widgets'), filename: 'common/common.js', - publicPath: Consts.CONTEXT_PATH + publicPath: CONTEXT_PATH }, module, plugins: [