diff --git a/.gitignore b/.gitignore index b6cfa424ad..a84d25d46d 100644 --- a/.gitignore +++ b/.gitignore @@ -83,4 +83,7 @@ typings/ .DS_Store # Nova Editor -.nova \ No newline at end of file +.nova + +# swap files (linux) +*.swp diff --git a/hosting/build/docker-compose.yaml b/hosting/build/docker-compose.yaml deleted file mode 100644 index 6988e3841b..0000000000 --- a/hosting/build/docker-compose.yaml +++ /dev/null @@ -1,16 +0,0 @@ -version: "3" - -services: - app-service: - build: ./server - volumes: - - ./server:/app - environment: - SELF_HOSTED: 1 - PORT: 4002 - - worker-service: - build: ./worker - environment: - SELF_HOSTED: 1, - PORT: 4003 diff --git a/hosting/build/server b/hosting/build/server deleted file mode 120000 index c40730cce5..0000000000 --- a/hosting/build/server +++ /dev/null @@ -1 +0,0 @@ -../../packages/server/ \ No newline at end of file diff --git a/hosting/build/worker b/hosting/build/worker deleted file mode 120000 index 8582fefbee..0000000000 --- a/hosting/build/worker +++ /dev/null @@ -1 +0,0 @@ -../../packages/worker/ \ No newline at end of file diff --git a/hosting/docker-compose.dev.yaml b/hosting/docker-compose.dev.yaml new file mode 100644 index 0000000000..39fcb7ec83 --- /dev/null +++ b/hosting/docker-compose.dev.yaml @@ -0,0 +1,76 @@ +version: "3" + +# optional ports are specified throughout for more advanced use cases. + +services: + minio-service: + container_name: budi-minio-dev + restart: always + image: minio/minio + volumes: + - minio_data:/data + ports: + - "${MINIO_PORT}:9000" + environment: + MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} + MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} + MINIO_BROWSER: "off" + command: server /data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 30s + timeout: 20s + retries: 3 + + proxy-service: + container_name: budi-envoy-dev + restart: always + image: envoyproxy/envoy:v1.16-latest + volumes: + - ./envoy.dev.yaml:/etc/envoy/envoy.yaml + ports: + - "${MAIN_PORT}:10000" + #- "9901:9901" + depends_on: + - minio-service + - couchdb-service + + couchdb-service: + container_name: budi-couchdb-dev + restart: always + image: apache/couchdb:3.0 + environment: + - COUCHDB_PASSWORD=${COUCH_DB_PASSWORD} + - COUCHDB_USER=${COUCH_DB_USER} + ports: + - "${COUCH_DB_PORT}:5984" + #- "4369:4369" + #- "9100:9100" + volumes: + - couchdb_data:/opt/couchdb/data + + couch-init: + image: curlimages/curl + environment: + PUT_CALL: "curl -u ${COUCH_DB_USER}:${COUCH_DB_PASSWORD} -X PUT couchdb-service:5984" + depends_on: + - couchdb-service + command: ["sh","-c","sleep 10 && $${PUT_CALL}/_users && $${PUT_CALL}/_replicator; fg;"] + + redis-service: + container_name: budi-redis-dev + restart: always + image: redis + ports: + - "${REDIS_PORT}:6379" + volumes: + - redis_data:/data + + +volumes: + couchdb_data: + driver: local + minio_data: + driver: local + redis_data: + driver: local diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index e8408d9a7d..86269837c2 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -11,13 +11,18 @@ services: - "${APP_PORT}:4002" environment: SELF_HOSTED: 1 - CLOUD: 1 COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984 WORKER_URL: http://worker-service:4003 + MINIO_URL: http://minio-service:9000 + MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} + MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} HOSTING_KEY: ${HOSTING_KEY} BUDIBASE_ENVIRONMENT: ${BUDIBASE_ENVIRONMENT} PORT: 4002 JWT_SECRET: ${JWT_SECRET} + LOG_LEVEL: info + SENTRY_DSN: https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131 + ENABLE_ANALYTICS: true depends_on: - worker-service @@ -28,7 +33,7 @@ services: ports: - "${WORKER_PORT}:4003" environment: - SELF_HOSTED: 1, + SELF_HOSTED: 1 PORT: 4003 MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY} MINIO_SECRET_KEY: ${MINIO_SECRET_KEY} @@ -93,9 +98,19 @@ services: depends_on: - couchdb-service command: ["sh","-c","sleep 10 && $${PUT_CALL}/_users && $${PUT_CALL}/_replicator; fg;"] + + redis-service: + restart: always + image: redis + ports: + - "${REDIS_PORT}:6379" + volumes: + - redis_data:/data volumes: couchdb_data: driver: local minio_data: driver: local + redis_data: + driver: local diff --git a/hosting/envoy.dev.yaml b/hosting/envoy.dev.yaml new file mode 100644 index 0000000000..e12bc2c0e0 --- /dev/null +++ b/hosting/envoy.dev.yaml @@ -0,0 +1,79 @@ +static_resources: + listeners: + - name: main_listener + address: + socket_address: { address: 0.0.0.0, port_value: 10000 } + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress + codec_type: auto + route_config: + name: local_route + virtual_hosts: + - name: local_services + domains: ["*"] + routes: + - match: { prefix: "/db/" } + route: + cluster: couchdb-service + prefix_rewrite: "/" + + - match: { prefix: "/cache/" } + route: + cluster: redis-service + prefix_rewrite: "/" + + # minio is on the default route because this works + # best, minio + AWS SDK doesn't handle path proxy + - match: { prefix: "/" } + route: + cluster: minio-service + + http_filters: + - name: envoy.filters.http.router + + clusters: + - name: minio-service + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: minio-service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: minio-service + port_value: 9000 + + - name: couchdb-service + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: couchdb-service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: couchdb-service + port_value: 5984 + + - name: redis-service + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: redis-service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: redis-service + port_value: 6379 diff --git a/hosting/envoy.yaml b/hosting/envoy.yaml index 11f5c81b99..8c6081d1a7 100644 --- a/hosting/envoy.yaml +++ b/hosting/envoy.yaml @@ -36,6 +36,11 @@ static_resources: cluster: worker-service prefix_rewrite: "/" + - match: { prefix: "/cache/" } + route: + cluster: redis-service + prefix_rewrite: "/" + - match: { prefix: "/db/" } route: cluster: couchdb-service @@ -107,3 +112,18 @@ static_resources: address: couchdb-service port_value: 5984 + - name: redis-service + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: redis-service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: redis-service + port_value: 6379 + + diff --git a/hosting/hosting.properties b/hosting/hosting.properties index ad047a3826..138e66d629 100644 --- a/hosting/hosting.properties +++ b/hosting/hosting.properties @@ -18,4 +18,5 @@ APP_PORT=4002 WORKER_PORT=4003 MINIO_PORT=4004 COUCH_DB_PORT=4005 +REDIS_PORT=6379 BUDIBASE_ENVIRONMENT=PRODUCTION diff --git a/package.json b/package.json index 037e9c4b1c..c097e62420 100644 --- a/package.json +++ b/package.json @@ -35,5 +35,6 @@ "test:e2e:ci": "lerna run cy:ci", "build:docker": "cd hosting/scripts/linux/ && ./release-to-docker-hub.sh && cd -", "build:docker:staging": "cd hosting/scripts/linux/ && ./release-to-docker-hub.sh staging && cd -" - } + }, + "dependencies": {} } diff --git a/packages/builder/cypress.json b/packages/builder/cypress.json index 09b38ae985..84c50ae863 100644 --- a/packages/builder/cypress.json +++ b/packages/builder/cypress.json @@ -3,6 +3,7 @@ "video": true, "projectId": "bmbemn", "env": { - "PORT": "4001" + "PORT": "4001", + "JWT_SECRET": "test" } } diff --git a/packages/builder/cypress/setup.js b/packages/builder/cypress/setup.js index 6848d38cba..7b849eb887 100644 --- a/packages/builder/cypress/setup.js +++ b/packages/builder/cypress/setup.js @@ -1,25 +1,26 @@ -// What this script does: -// 1. Removes the old test folder if it exists (.budibase) -// 2. Initialises using `.budibase` -// 3. Runs the server using said folder - -const { join, resolve } = require("path") -const initialiseBudibase = require("../../server/src/utilities/initialiseBudibase") const cypressConfig = require("../cypress.json") +const path = require("path") -const homedir = join(require("os").homedir(), ".budibase") +const tmpdir = path.join(require("os").tmpdir(), ".budibase") process.env.BUDIBASE_API_KEY = "6BE826CB-6B30-4AEC-8777-2E90464633DE" process.env.NODE_ENV = "cypress" process.env.ENABLE_ANALYTICS = "false" process.env.PORT = cypressConfig.env.PORT +process.env.JWT_SECRET = cypressConfig.env.JWT_SECRET +process.env.COUCH_URL = `leveldb://${tmpdir}/.data/` +process.env.SELF_HOSTED = 1 +process.env.MINIO_URL = "http://localhost:10000/" +process.env.MINIO_ACCESS_KEY = "budibase" +process.env.MINIO_SECRET_KEY = "budibase" +process.env.COUCH_DB_USER = "budibase" +process.env.COUCH_DB_PASSWORD = "budibase" // Stop info logs polluting test outputs process.env.LOG_LEVEL = "error" -async function run(dir) { - process.env.BUDIBASE_DIR = resolve(dir) - require("dotenv").config({ path: resolve(dir, ".env") }) +async function run() { + // require("dotenv").config({ path: resolve(dir, ".env") }) // dont make this a variable or top level require // it will cause environment module to be loaded prematurely @@ -27,12 +28,15 @@ async function run(dir) { server.on("close", () => console.log("Server Closed")) } -initialiseBudibase({ dir: homedir, clientId: "cypress-test" }) - .then(() => { - delete require.cache[require.resolve("../../server/src/environment")] - const xPlatHomeDir = homedir.startsWith("~") - ? join(homedir(), homedir.substring(1)) - : homedir - run(xPlatHomeDir) - }) - .catch(e => console.error(e)) +run() + +// TODO: ensure that this still works +// initialiseBudibase({ dir: homedir, clientId: "cypress-test" }) +// .then(() => { +// delete require.cache[require.resolve("../../server/src/environment")] +// const xPlatHomeDir = homedir.startsWith("~") +// ? join(homedir(), homedir.substring(1)) +// : homedir +// run(xPlatHomeDir) +// }) +// .catch(e => console.error(e)) diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index 1e2ee46cad..43cd92e9e2 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -7,6 +7,8 @@ import WebhookDisplay from "../Shared/WebhookDisplay.svelte" import DrawerBindableInput from "../../common/DrawerBindableInput.svelte" import AutomationBindingPanel from "./AutomationBindingPanel.svelte" + import Editor from "components/integration/QueryEditor.svelte" + import CodeEditorModal from "./CodeEditorModal.svelte" export let block export let webhookModal @@ -80,6 +82,17 @@ {:else if value.customType === 'triggerSchema'} + {:else if value.customType === 'code'} + +
{JSON.stringify(bindings, null, 2)}
+ { + block.inputs[key] = e.detail.value + }} + value={block.inputs[key]} + /> +
{:else if value.type === 'string' || value.type === 'number'} + import { Button, Modal, ModalContent } from "@budibase/bbui" + + let modal + + export const show = () => { + modal.show() + } + export const hide = () => { + modal.hide() + } + + + + +
+ +
+
+
+ + + diff --git a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte b/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte index 7570cd6c5e..8c2ea880ae 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte @@ -27,7 +27,7 @@ notifier.success("Datasource deleted") // navigate to first index page if the source you are deleting is selected if (wasSelectedSource === datasource._id) { - $goto('./datasource') + $goto("./datasource") } hideEditor() } diff --git a/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte b/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte index 60886b5be1..0a186375d8 100644 --- a/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte +++ b/packages/builder/src/components/backend/TableNavigator/popovers/EditTablePopover.svelte @@ -37,13 +37,13 @@ } async function deleteTable() { - const wasSelectedTable = $backendUiStore.selectedTable + const wasSelectedTable = $backendUiStore.selectedTable await backendUiStore.actions.tables.delete(table) store.actions.screens.delete(templateScreens) await backendUiStore.actions.tables.fetch() notifier.success("Table deleted") if (wasSelectedTable._id === table._id) { - $goto('./table') + $goto("./table") } hideEditor() } diff --git a/packages/builder/src/components/integration/QueryEditor.svelte b/packages/builder/src/components/integration/QueryEditor.svelte index c4d42e9812..7af5737836 100644 --- a/packages/builder/src/components/integration/QueryEditor.svelte +++ b/packages/builder/src/components/integration/QueryEditor.svelte @@ -132,6 +132,10 @@ if (destroyed) return + CodeMirror.commands.autocomplete = function(cm) { + CodeMirror.showHint(cm, CodeMirror.hint.javascript) + } + editor = CodeMirror.fromTextArea(refs.editor, opts) editor.on("change", instance => { diff --git a/packages/cli/src/hosting/makeEnv.js b/packages/cli/src/hosting/makeEnv.js index c8359dd5e4..318a72def1 100644 --- a/packages/cli/src/hosting/makeEnv.js +++ b/packages/cli/src/hosting/makeEnv.js @@ -26,6 +26,7 @@ APP_PORT=4002 WORKER_PORT=4003 MINIO_PORT=4004 COUCH_DB_PORT=4005 +REDIS_PORT=6379 BUDIBASE_ENVIRONMENT=PRODUCTION` } diff --git a/packages/server/.env.template b/packages/server/.env.template deleted file mode 100644 index b2ff5be3f4..0000000000 --- a/packages/server/.env.template +++ /dev/null @@ -1,17 +0,0 @@ -# url of couch db, including username and password -# http://admin:password@localhost:5984 -COUCH_DB_URL={{couchDbUrl}} - -# identifies a client database - i.e. group of apps -CLIENT_ID={{clientId}} - -# used to create cookie hashes -JWT_SECRET={{cookieKey1}} - -# error level for koa-pino -LOG_LEVEL=info - -DEPLOYMENT_CREDENTIALS_URL="https://dt4mpwwap8.execute-api.eu-west-1.amazonaws.com/prod/" -DEPLOYMENT_DB_URL="https://couchdb.budi.live:5984" -SENTRY_DSN=https://a34ae347621946bf8acded18e5b7d4b8@o420233.ingest.sentry.io/5338131 -ENABLE_ANALYTICS="true" diff --git a/packages/server/Dockerfile b/packages/server/Dockerfile index d75fe1f5d0..c13022c2d6 100644 --- a/packages/server/Dockerfile +++ b/packages/server/Dockerfile @@ -2,7 +2,6 @@ FROM node:12-alpine WORKDIR /app -ENV CLOUD=1 ENV PORT=4001 ENV COUCH_DB_URL=https://couchdb.budi.live:5984 ENV BUDIBASE_ENVIRONMENT=PRODUCTION diff --git a/packages/server/package.json b/packages/server/package.json index 6746b01c89..406a29ed69 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -36,11 +36,13 @@ "test:integration": "jest --coverage --detectOpenHandles", "test:watch": "jest --watch", "run:docker": "node src/index", - "dev:builder": "cross-env PORT=4001 nodemon src/index.js", + "dev:stack:up": "node scripts/dev/manage.js up", + "dev:stack:down": "node scripts/dev/manage.js down", + "dev:stack:nuke": "node scripts/dev/manage.js nuke", + "dev:builder": "npm run dev:stack:up && nodemon src/index.js", "electron": "electron src/electron.js", "build:electron": "electron-builder --dir", "publish:electron": "electron-builder -mwl --publish always", - "postinstall": "electron-builder install-app-deps", "lint": "eslint --fix src/", "initialise": "node scripts/initialise.js" }, @@ -63,7 +65,7 @@ "!src/tests/**/*", "!src/automations/tests/**/*", "!src/utilities/fileProcessor.js", - "!src/utilities/initialiseBudibase.js" + "!src/utilities/fileSystem/**/*" ], "coverageReporters": [ "lcov", @@ -87,6 +89,8 @@ "arangojs": "7.2.0", "aws-sdk": "^2.767.0", "bcryptjs": "2.4.3", + "bull": "^3.22.0", + "bull-board": "^1.5.1", "chmodr": "1.2.0", "csvtojson": "2.0.10", "dotenv": "8.2.0", @@ -104,6 +108,7 @@ "koa": "2.7.0", "koa-body": "4.2.0", "koa-compress": "4.0.1", + "koa-connect": "^2.1.0", "koa-pino-logger": "3.0.0", "koa-send": "5.0.0", "koa-session": "5.12.0", @@ -134,7 +139,7 @@ "devDependencies": { "@budibase/standard-components": "^0.8.9", "@jest/test-sequencer": "^24.8.0", - "cross-env": "^7.0.3", + "docker-compose": "^0.23.6", "electron": "10.1.3", "electron-builder": "^22.9.1", "electron-builder-notarize": "^1.1.2", diff --git a/packages/server/scripts/dev/manage.js b/packages/server/scripts/dev/manage.js new file mode 100644 index 0000000000..42ac009e72 --- /dev/null +++ b/packages/server/scripts/dev/manage.js @@ -0,0 +1,97 @@ +#!/usr/bin/env node +const compose = require("docker-compose") +const path = require("path") +const fs = require("fs") + +// This script wraps docker-compose allowing you to manage your dev infrastructure with simple commands. +const CONFIG = { + cwd: path.resolve(process.cwd(), "../../hosting"), + config: "docker-compose.dev.yaml", + log: true, +} + +const Commands = { + Up: "up", + Down: "down", + Nuke: "nuke", +} + +async function init() { + const envFilePath = path.join(process.cwd(), ".env") + if (fs.existsSync(envFilePath)) { + return + } + const envFileJson = { + PORT: 4001, + MINIO_URL: "http://localhost:10000/", + COUCH_DB_URL: "http://budibase:budibase@localhost:10000/db/", + // REDIS_URL: "http://localhost:10000/cache/", + WORKER_URL: "http://localhost:4002", + JWT_SECRET: "testsecret", + MINIO_ACCESS_KEY: "budibase", + MINIO_SECRET_KEY: "budibase", + COUCH_DB_PASSWORD: "budibase", + COUCH_DB_USER: "budibase", + SELF_HOSTED: 1, + } + let envFile = "" + Object.keys(envFileJson).forEach(key => { + envFile += `${key}=${envFileJson[key]}\n` + }) + fs.writeFileSync(envFilePath, envFile) +} + +async function up() { + console.log("Spinning up your budibase dev environment... πŸ”§βœ¨") + await init() + await compose.upAll(CONFIG) +} + +async function down() { + console.log("Spinning down your budibase dev environment... πŸŒ‡") + await compose.stop(CONFIG) +} + +async function nuke() { + console.log( + "Clearing down your budibase dev environment, including all containers and volumes... πŸ’₯" + ) + await compose.down(CONFIG) +} + +const managementCommand = process.argv.slice(2)[0] + +if ( + !managementCommand || + !Object.values(Commands).some(command => managementCommand === command) +) { + throw new Error( + "You must supply either an 'up', 'down' or 'nuke' commmand to manage the budibase development environment." + ) +} + +let command +switch (managementCommand) { + case Commands.Up: + command = up + break + case Commands.Down: + command = down + break + case Commands.Nuke: + command = nuke + break + default: + command = up +} + +command() + .then(() => { + console.log("Done! πŸŽ‰") + }) + .catch(err => { + console.error( + "Something went wrong while managing budibase dev environment:", + err.message + ) + }) diff --git a/packages/server/scripts/exportAppTemplate.js b/packages/server/scripts/exportAppTemplate.js index e896917d5b..c6d738225c 100755 --- a/packages/server/scripts/exportAppTemplate.js +++ b/packages/server/scripts/exportAppTemplate.js @@ -1,6 +1,9 @@ #!/usr/bin/env node -const { exportTemplateFromApp } = require("../src/utilities/templates") const yargs = require("yargs") +const fs = require("fs") +const { join } = require("path") +const CouchDB = require("../src/db") +const { budibaseAppsDir } = require("../src/utilities/budibaseDir") // Script to export a chosen budibase app into a package // Usage: ./scripts/exportAppTemplate.js export --name=Funky --appId=appId @@ -22,18 +25,22 @@ yargs }, }, async args => { + const name = args.name, + appId = args.appId console.log("Exporting app..") - if (args.name == null || args.appId == null) { + if (name == null || appId == null) { console.error( "Unable to export without a name and app ID being specified, check help for more info." ) return } - const exportPath = await exportTemplateFromApp({ - templateName: args.name, - appId: args.appId, - }) - console.log(`Template ${args.name} exported to ${exportPath}`) + const exportPath = join(budibaseAppsDir(), "templates", "app", name, "db") + fs.ensureDirSync(exportPath) + const writeStream = fs.createWriteStream(join(exportPath, "dump.text")) + // perform couch dump + const instanceDb = new CouchDB(appId) + await instanceDb.dump(writeStream, {}) + console.log(`Template ${name} exported to ${exportPath}`) } ) .help() diff --git a/packages/server/scripts/initialise.js b/packages/server/scripts/initialise.js deleted file mode 100644 index 485ebd56a0..0000000000 --- a/packages/server/scripts/initialise.js +++ /dev/null @@ -1,17 +0,0 @@ -const { join } = require("path") -const { homedir } = require("os") - -const initialiseBudibase = require("../src/utilities/initialiseBudibase") -const DIRECTORY = "~/.budibase" - -function run() { - let opts = {} - let dir = DIRECTORY - opts.quiet = true - opts.dir = dir.startsWith("~") ? join(homedir(), dir.substring(1)) : dir - return initialiseBudibase(opts) -} - -run().then(() => { - console.log("Init complete.") -}) diff --git a/packages/server/src/api/controllers/apikeys.js b/packages/server/src/api/controllers/apikeys.js index 96754f17cc..1c8caba1cb 100644 --- a/packages/server/src/api/controllers/apikeys.js +++ b/packages/server/src/api/controllers/apikeys.js @@ -1,56 +1,32 @@ -const fs = require("fs") -const { join } = require("../../utilities/centralPath") -const readline = require("readline") -const { budibaseAppsDir } = require("../../utilities/budibaseDir") -const env = require("../../environment") -const ENV_FILE_PATH = "/.env" +const builderDB = require("../../db/builder") exports.fetch = async function(ctx) { - ctx.status = 200 - ctx.body = { - budibase: env.BUDIBASE_API_KEY, - userId: env.USERID_API_KEY, + try { + const mainDoc = await builderDB.getBuilderMainDoc() + ctx.body = mainDoc.apiKeys ? mainDoc.apiKeys : {} + } catch (err) { + /* istanbul ignore next */ + ctx.throw(400, err) } } exports.update = async function(ctx) { - const key = `${ctx.params.key.toUpperCase()}_API_KEY` + const key = ctx.params.key const value = ctx.request.body.value - // set environment variables - env._set(key, value) - - // Write to file - await updateValues([key, value]) - - ctx.status = 200 - ctx.message = `Updated ${ctx.params.key} API key succesfully.` - ctx.body = { [ctx.params.key]: ctx.request.body.value } -} - -async function updateValues([key, value]) { - let newContent = "" - let keyExists = false - let envPath = join(budibaseAppsDir(), ENV_FILE_PATH) - const readInterface = readline.createInterface({ - input: fs.createReadStream(envPath), - output: process.stdout, - console: false, - }) - readInterface.on("line", function(line) { - // Mutate lines and change API Key - if (line.startsWith(key)) { - line = `${key}=${value}` - keyExists = true + try { + const mainDoc = await builderDB.getBuilderMainDoc() + if (mainDoc.apiKeys == null) { + mainDoc.apiKeys = {} } - newContent = `${newContent}\n${line}` - }) - readInterface.on("close", function() { - // Write file here - if (!keyExists) { - // Add API Key if it doesn't exist in the file at all - newContent = `${newContent}\n${key}=${value}` + mainDoc.apiKeys[key] = value + const resp = await builderDB.setBuilderMainDoc(mainDoc) + ctx.body = { + _id: resp.id, + _rev: resp.rev, } - fs.writeFileSync(envPath, newContent) - }) + } catch (err) { + /* istanbul ignore next */ + ctx.throw(400, err) + } } diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 9e50319d5d..8f4cfaf24d 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -1,15 +1,14 @@ const CouchDB = require("../../db") -const compileStaticAssets = require("../../utilities/builder/compileStaticAssets") const env = require("../../environment") -const { existsSync } = require("fs-extra") -const { budibaseAppsDir } = require("../../utilities/budibaseDir") const setBuilderToken = require("../../utilities/builder/setBuilderToken") -const fs = require("fs-extra") -const { join, resolve } = require("../../utilities/centralPath") const packageJson = require("../../../package.json") const { createLinkView } = require("../../db/linkedRows") const { createRoutingView } = require("../../utilities/routing") -const { downloadTemplate } = require("../../utilities/templates") +const { + getTemplateStream, + createApp, + deleteApp, +} = require("../../utilities/fileSystem") const { generateAppID, getLayoutParams, @@ -20,9 +19,6 @@ const { BUILTIN_ROLE_IDS, AccessController, } = require("../../utilities/security/roles") -const { - downloadExtractComponentLibraries, -} = require("../../utilities/createAppPackage") const { BASE_LAYOUTS } = require("../../constants/layouts") const { createHomeScreen, @@ -32,11 +28,7 @@ const { cloneDeep } = require("lodash/fp") const { processObject } = require("@budibase/string-templates") const { getAllApps } = require("../../utilities") const { USERS_TABLE_SCHEMA } = require("../../constants") -const { - getDeployedApps, - getHostingInfo, - HostingTypes, -} = require("../../utilities/builder/hosting") +const { getDeployedApps } = require("../../utilities/builder/hosting") const URL_REGEX_SLASH = /\/|\\/g @@ -75,8 +67,7 @@ async function getAppUrlIfNotInUse(ctx) { url = encodeURI(`${ctx.request.body.name}`) } url = `/${url.replace(URL_REGEX_SLASH, "")}`.toLowerCase() - const hostingInfo = await getHostingInfo() - if (hostingInfo.type === HostingTypes.CLOUD) { + if (!env.SELF_HOSTED) { return url } const deployedApps = await getDeployedApps() @@ -106,16 +97,7 @@ async function createInstance(template) { // this is currently very hard to test, downloading and importing template files /* istanbul ignore next */ if (template && template.useTemplate === "true") { - let dbDumpReadStream - if (template.file) { - dbDumpReadStream = fs.createReadStream(template.file.path) - } else { - const templatePath = await downloadTemplate(...template.key.split("/")) - dbDumpReadStream = fs.createReadStream( - join(templatePath, "db", "dump.txt") - ) - } - const { ok } = await db.load(dbDumpReadStream) + const { ok } = await db.load(await getTemplateStream(template)) if (!ok) { throw "Error loading database dump from template." } @@ -190,10 +172,10 @@ exports.create = async function(ctx) { const instanceDb = new CouchDB(appId) await instanceDb.put(newApplication) - const newAppFolder = await createEmptyAppPackage(ctx, newApplication) + await createEmptyAppPackage(ctx, newApplication) /* istanbul ignore next */ if (env.NODE_ENV !== "jest") { - await downloadExtractComponentLibraries(newAppFolder) + await createApp(appId) } await setBuilderToken(ctx, appId, version) @@ -223,10 +205,9 @@ exports.delete = async function(ctx) { const app = await db.get(ctx.params.appId) const result = await db.destroy() - // remove top level directory - await fs.rmdir(join(budibaseAppsDir(), ctx.params.appId), { - recursive: true, - }) + if (env.NODE_ENV !== "jest") { + await deleteApp(ctx.params.appId) + } ctx.status = 200 ctx.message = `Application ${app.name} deleted successfully.` @@ -234,17 +215,8 @@ exports.delete = async function(ctx) { } const createEmptyAppPackage = async (ctx, app) => { - const appsFolder = budibaseAppsDir() - const newAppFolder = resolve(appsFolder, app._id) - const db = new CouchDB(app._id) - if (existsSync(newAppFolder)) { - ctx.throw(400, "App folder already exists for this application") - } - - fs.mkdirpSync(newAppFolder) - let screensAndLayouts = [] for (let layout of BASE_LAYOUTS) { const cloned = cloneDeep(layout) @@ -260,6 +232,4 @@ const createEmptyAppPackage = async (ctx, app) => { screensAndLayouts.push(loginScreen) await db.bulkDocs(screensAndLayouts) - await compileStaticAssets(app._id) - return newAppFolder } diff --git a/packages/server/src/api/controllers/auth.js b/packages/server/src/api/controllers/auth.js index e5c0f9a029..fc486bcb50 100644 --- a/packages/server/src/api/controllers/auth.js +++ b/packages/server/src/api/controllers/auth.js @@ -45,9 +45,9 @@ exports.authenticate = async ctx => { roleId: dbUser.roleId, version: app.version, } - // if in cloud add the user api key, unless self hosted + // if in prod add the user api key, unless self hosted /* istanbul ignore next */ - if (env.CLOUD && !env.SELF_HOSTED) { + if (env.isProd() && !env.SELF_HOSTED) { const { apiKey } = await getAPIKey(ctx.user.appId) payload.apiKey = apiKey } diff --git a/packages/server/src/api/controllers/backup.js b/packages/server/src/api/controllers/backup.js index a83f96165b..02be10bbec 100644 --- a/packages/server/src/api/controllers/backup.js +++ b/packages/server/src/api/controllers/backup.js @@ -1,28 +1,10 @@ -const { performDump } = require("../../utilities/templates") -const path = require("path") -const os = require("os") -const fs = require("fs-extra") +const { performBackup } = require("../../utilities/fileSystem") exports.exportAppDump = async function(ctx) { const { appId } = ctx.query - const appname = decodeURI(ctx.query.appname) - - const backupsDir = path.join(os.homedir(), ".budibase", "backups") - fs.ensureDirSync(backupsDir) - const backupIdentifier = `${appname}Backup${new Date().getTime()}.txt` - await performDump({ - dir: backupsDir, - appId, - name: backupIdentifier, - }) - - ctx.status = 200 - - const backupFile = path.join(backupsDir, backupIdentifier) - ctx.attachment(backupIdentifier) - ctx.body = fs.createReadStream(backupFile) + ctx.body = await performBackup(appId, backupIdentifier) } diff --git a/packages/server/src/api/controllers/component.js b/packages/server/src/api/controllers/component.js index 092d154817..35a91fe4eb 100644 --- a/packages/server/src/api/controllers/component.js +++ b/packages/server/src/api/controllers/component.js @@ -1,44 +1,37 @@ const CouchDB = require("../../db") -const { resolve, join } = require("../../utilities/centralPath") -const { - budibaseTempDir, - budibaseAppsDir, -} = require("../../utilities/budibaseDir") +const { join } = require("../../utilities/centralPath") +const { budibaseTempDir } = require("../../utilities/budibaseDir") +const fileSystem = require("../../utilities/fileSystem") +const env = require("../../environment") exports.fetchAppComponentDefinitions = async function(ctx) { const appId = ctx.params.appId || ctx.appId const db = new CouchDB(appId) const app = await db.get(appId) - ctx.body = app.componentLibraries.reduce((acc, componentLibrary) => { - let appDirectory = resolve(budibaseAppsDir(), appId, "node_modules") - - if (ctx.isDev) { - appDirectory = budibaseTempDir() - } - - const componentJson = require(join( - appDirectory, - componentLibrary, - ctx.isDev ? "" : "package", - "manifest.json" - )) - - const result = {} - - // map over the components.json and add the library identifier as a key - // button -> @budibase/standard-components/button - for (let key of Object.keys(componentJson)) { - const fullComponentName = `${componentLibrary}/${key}`.toLowerCase() - result[fullComponentName] = { + let componentManifests = await Promise.all( + app.componentLibraries.map(async library => { + let manifest + if (env.isDev()) { + manifest = require(join(budibaseTempDir(), library, "manifest.json")) + } else { + manifest = await fileSystem.getComponentLibraryManifest(appId, library) + } + return { + manifest, + library, + } + }) + ) + const definitions = {} + for (let { manifest, library } of componentManifests) { + for (let key of Object.keys(manifest)) { + const fullComponentName = `${library}/${key}`.toLowerCase() + definitions[fullComponentName] = { component: fullComponentName, - ...componentJson[key], + ...manifest[key], } } - - return { - ...acc, - ...result, - } - }, {}) + } + ctx.body = definitions } diff --git a/packages/server/src/api/controllers/deploy/awsDeploy.js b/packages/server/src/api/controllers/deploy/awsDeploy.js index 18c9279515..2d34bc1b04 100644 --- a/packages/server/src/api/controllers/deploy/awsDeploy.js +++ b/packages/server/src/api/controllers/deploy/awsDeploy.js @@ -66,12 +66,7 @@ exports.deploy = async function(deployment) { const appId = deployment.getAppId() const { bucket, accountId } = deployment.getVerification() const metadata = { accountId } - const s3Client = new AWS.S3({ - params: { - Bucket: bucket, - }, - }) - await deployToObjectStore(appId, s3Client, metadata) + await deployToObjectStore(appId, bucket, metadata) } exports.replicateDb = async function(deployment) { diff --git a/packages/server/src/api/controllers/deploy/selfDeploy.js b/packages/server/src/api/controllers/deploy/selfDeploy.js index 81fa72cae5..444e7cd873 100644 --- a/packages/server/src/api/controllers/deploy/selfDeploy.js +++ b/packages/server/src/api/controllers/deploy/selfDeploy.js @@ -7,7 +7,6 @@ const { const { getWorkerUrl, getCouchUrl, - getMinioUrl, getSelfHostKey, } = require("../../../utilities/builder/hosting") @@ -45,17 +44,9 @@ exports.postDeployment = async function() { exports.deploy = async function(deployment) { const appId = deployment.getAppId() const verification = deployment.getVerification() - const objClient = new AWS.S3({ - endpoint: await getMinioUrl(), - s3ForcePathStyle: true, // needed with minio? - signatureVersion: "v4", - params: { - Bucket: verification.bucket, - }, - }) // no metadata, aws has account ID in metadata const metadata = {} - await deployToObjectStore(appId, objClient, metadata) + await deployToObjectStore(appId, verification.bucket, metadata) } exports.replicateDb = async function(deployment) { diff --git a/packages/server/src/api/controllers/deploy/utils.js b/packages/server/src/api/controllers/deploy/utils.js index 3536a6f630..de608acfb1 100644 --- a/packages/server/src/api/controllers/deploy/utils.js +++ b/packages/server/src/api/controllers/deploy/utils.js @@ -1,16 +1,24 @@ -const fs = require("fs") -const sanitize = require("sanitize-s3-objectkey") -const { walkDir } = require("../../../utilities") const { join } = require("../../../utilities/centralPath") +const fs = require("fs") const { budibaseAppsDir } = require("../../../utilities/budibaseDir") const fetch = require("node-fetch") const PouchDB = require("../../../db") const CouchDB = require("pouchdb") +const { upload } = require("../../../utilities/fileSystem") -const CONTENT_TYPE_MAP = { - html: "text/html", - css: "text/css", - js: "application/javascript", +// TODO: everything in this file is to be removed + +function walkDir(dirPath, callback) { + for (let filename of fs.readdirSync(dirPath)) { + const filePath = `${dirPath}/${filename}` + const stat = fs.lstatSync(filePath) + + if (stat.isFile()) { + callback(filePath) + } else { + walkDir(filePath, callback) + } + } } exports.fetchCredentials = async function(url, body) { @@ -34,30 +42,25 @@ exports.fetchCredentials = async function(url, body) { return json } -exports.prepareUpload = async function({ s3Key, metadata, client, file }) { - const extension = [...file.name.split(".")].pop() - const fileBytes = fs.readFileSync(file.path) - - const upload = await client - .upload({ - // windows file paths need to be converted to forward slashes for s3 - Key: sanitize(s3Key).replace(/\\/g, "/"), - Body: fileBytes, - ContentType: file.type || CONTENT_TYPE_MAP[extension.toLowerCase()], - Metadata: metadata, - }) - .promise() +exports.prepareUpload = async function({ s3Key, bucket, metadata, file }) { + const response = await upload({ + bucket, + metadata, + filename: s3Key, + path: file.path, + type: file.type, + }) return { size: file.size, name: file.name, - extension, - url: upload.Location, - key: upload.Key, + extension: [...file.name.split(".")].pop(), + url: response.Location, + key: response.Key, } } -exports.deployToObjectStore = async function(appId, objectClient, metadata) { +exports.deployToObjectStore = async function(appId, bucket, metadata) { const appAssetsPath = join(budibaseAppsDir(), appId, "public") let uploads = [] @@ -66,12 +69,12 @@ exports.deployToObjectStore = async function(appId, objectClient, metadata) { walkDir(appAssetsPath, function(filePath) { const filePathParts = filePath.split("/") const appAssetUpload = exports.prepareUpload({ + bucket, file: { path: filePath, name: filePathParts.pop(), }, s3Key: filePath.replace(appAssetsPath, `assets/${appId}`), - client: objectClient, metadata, }) uploads.push(appAssetUpload) @@ -92,7 +95,7 @@ exports.deployToObjectStore = async function(appId, objectClient, metadata) { const attachmentUpload = exports.prepareUpload({ file, s3Key: `assets/${appId}/attachments/${file.processedFileName}`, - client: objectClient, + bucket, metadata, }) diff --git a/packages/server/src/api/controllers/hosting.js b/packages/server/src/api/controllers/hosting.js index 1d1884eb52..4b070cf75b 100644 --- a/packages/server/src/api/controllers/hosting.js +++ b/packages/server/src/api/controllers/hosting.js @@ -1,11 +1,11 @@ const CouchDB = require("../../db") -const { BUILDER_CONFIG_DB, HOSTING_DOC } = require("../../constants") const { getHostingInfo, getDeployedApps, HostingTypes, getAppUrl, } = require("../../utilities/builder/hosting") +const { StaticDatabases } = require("../../db/utils") exports.fetchInfo = async ctx => { ctx.body = { @@ -14,17 +14,17 @@ exports.fetchInfo = async ctx => { } exports.save = async ctx => { - const db = new CouchDB(BUILDER_CONFIG_DB) + const db = new CouchDB(StaticDatabases.BUILDER_HOSTING.name) const { type } = ctx.request.body if (type === HostingTypes.CLOUD && ctx.request.body._rev) { ctx.body = await db.remove({ ...ctx.request.body, - _id: HOSTING_DOC, + _id: StaticDatabases.BUILDER_HOSTING.baseDoc, }) } else { ctx.body = await db.put({ ...ctx.request.body, - _id: HOSTING_DOC, + _id: StaticDatabases.BUILDER_HOSTING.baseDoc, }) } } diff --git a/packages/server/src/api/controllers/query.js b/packages/server/src/api/controllers/query.js index a2badb0d0d..b9b7c85427 100644 --- a/packages/server/src/api/controllers/query.js +++ b/packages/server/src/api/controllers/query.js @@ -93,7 +93,7 @@ exports.find = async function(ctx) { const db = new CouchDB(ctx.user.appId) const query = enrichQueries(await db.get(ctx.params.queryId)) // remove properties that could be dangerous in real app - if (env.CLOUD) { + if (env.isProd()) { delete query.fields delete query.parameters delete query.schema diff --git a/packages/server/src/api/controllers/script.js b/packages/server/src/api/controllers/script.js index 060165b1b8..297833596e 100644 --- a/packages/server/src/api/controllers/script.js +++ b/packages/server/src/api/controllers/script.js @@ -1,23 +1,21 @@ -const CouchDB = require("../../db") +const fetch = require("node-fetch") const vm = require("vm") class ScriptExecutor { - constructor(script) { - this.script = script + constructor(body) { + this.script = new vm.Script(body.script) + this.context = vm.createContext(body.context) + this.context.fetch = fetch } execute() { - vm.runInNewContext(this.script, { - require: require, - console: console, - }) + this.script.runInContext(this.context) + return this.context } } exports.execute = async function(ctx) { - const appId = ctx.user.appId - - const executor = new ScriptExecutor(ctx.request.body.script) + const executor = new ScriptExecutor(ctx.request.body) const result = executor.execute() diff --git a/packages/server/src/api/controllers/static/index.js b/packages/server/src/api/controllers/static/index.js index ff1ecf5b2e..1da98d1940 100644 --- a/packages/server/src/api/controllers/static/index.js +++ b/packages/server/src/api/controllers/static/index.js @@ -3,9 +3,7 @@ require("svelte/register") const send = require("koa-send") const { resolve, join } = require("../../../utilities/centralPath") const fetch = require("node-fetch") -const fs = require("fs-extra") const uuid = require("uuid") -const AWS = require("aws-sdk") const { prepareUpload } = require("../deploy/utils") const { processString } = require("@budibase/string-templates") const { @@ -15,9 +13,10 @@ const { const { getDeployedApps } = require("../../../utilities/builder/hosting") const CouchDB = require("../../../db") const setBuilderToken = require("../../../utilities/builder/setBuilderToken") -const fileProcessor = require("../../../utilities/fileProcessor") +const { loadHandlebarsFile } = require("../../../utilities/fileSystem") const env = require("../../../environment") const { OBJ_STORE_DIRECTORY } = require("../../../constants") +const fileProcessor = require("../../../utilities/fileSystem/processor") function objectStoreUrl() { if (env.SELF_HOSTED) { @@ -50,106 +49,31 @@ exports.serveBuilder = async function(ctx) { await send(ctx, ctx.file, { root: ctx.devPath || builderPath }) } -exports.serveSelfHostPage = async function(ctx) { - const logo = fs.readFileSync(resolve(__dirname, "selfhost/logo.svg"), "utf8") - const hostingHbs = fs.readFileSync( - resolve(__dirname, "selfhost/index.hbs"), - "utf8" - ) - ctx.body = await processString(hostingHbs, { - logo, - }) -} - exports.uploadFile = async function(ctx) { - let files - files = + let files = ctx.request.files.file.length > 1 ? Array.from(ctx.request.files.file) : [ctx.request.files.file] - const attachmentsPath = resolve( - budibaseAppsDir(), - ctx.user.appId, - "attachments" - ) - - if (env.CLOUD) { - // remote upload - const s3 = new AWS.S3({ - params: { - Bucket: "prod-budi-app-assets", - }, - }) - - const uploads = files.map(file => { - const fileExtension = [...file.name.split(".")].pop() - const processedFileName = `${uuid.v4()}.${fileExtension}` - - return prepareUpload({ - file, - s3Key: `assets/${ctx.user.appId}/attachments/${processedFileName}`, - s3, - }) - }) - - ctx.body = await Promise.all(uploads) - return - } - - ctx.body = await processLocalFileUploads({ - files, - outputPath: attachmentsPath, - appId: ctx.user.appId, - }) -} - -async function processLocalFileUploads({ files, outputPath, appId }) { - // create attachments dir if it doesnt exist - !fs.existsSync(outputPath) && fs.mkdirSync(outputPath, { recursive: true }) - - const filesToProcess = files.map(file => { + const uploads = files.map(async file => { const fileExtension = [...file.name.split(".")].pop() // filenames converted to UUIDs so they are unique const processedFileName = `${uuid.v4()}.${fileExtension}` - return { - name: file.name, - path: file.path, - size: file.size, - type: file.type, - processedFileName, + // need to handle image processing + await fileProcessor.process({ + ...file, extension: fileExtension, - outputPath: join(outputPath, processedFileName), - url: join("/attachments", processedFileName), - } + }) + + return prepareUpload({ + file, + s3Key: `assets/${ctx.user.appId}/attachments/${processedFileName}`, + bucket: "prod-budi-app-assets", + }) }) - const fileProcessOperations = filesToProcess.map(fileProcessor.process) - - const processedFiles = await Promise.all(fileProcessOperations) - - let pendingFileUploads - // local document used to track which files need to be uploaded - // db.get throws an error if the document doesn't exist - // need to use a promise to default - const db = new CouchDB(appId) - await db - .get("_local/fileuploads") - .then(data => { - pendingFileUploads = data - }) - .catch(() => { - pendingFileUploads = { _id: "_local/fileuploads", uploads: [] } - }) - - pendingFileUploads.uploads = [ - ...processedFiles, - ...pendingFileUploads.uploads, - ] - await db.put(pendingFileUploads) - - return processedFiles + ctx.body = await Promise.all(uploads) } exports.serveApp = async function(ctx) { @@ -163,12 +87,12 @@ exports.serveApp = async function(ctx) { const { head, html, css } = App.render({ title: appInfo.name, - production: env.CLOUD, + production: env.isProd(), appId, objectStoreUrl: objectStoreUrl(), }) - const appHbs = fs.readFileSync(`${__dirname}/templates/app.hbs`, "utf8") + const appHbs = loadHandlebarsFile(`${__dirname}/templates/app.hbs`) ctx.body = await processString(appHbs, { head, body: html, @@ -182,7 +106,7 @@ exports.serveAttachment = async function(ctx) { const attachmentsPath = resolve(budibaseAppsDir(), appId, "attachments") // Serve from object store - if (env.CLOUD) { + if (env.isProd()) { const S3_URL = join(objectStoreUrl(), appId, "attachments", ctx.file) const response = await fetch(S3_URL) const body = await response.text() @@ -213,15 +137,13 @@ exports.serveComponentLibrary = async function(ctx) { "dist" ) - if (ctx.isDev) { + if (env.isDev()) { componentLibraryPath = join( budibaseTempDir(), decodeURI(ctx.query.library), "dist" ) - } - - if (env.CLOUD) { + } else { let componentLib = "componentlibrary" if (ctx.user.version) { componentLib += `-${ctx.user.version}` diff --git a/packages/server/src/api/controllers/static/selfhost/index.hbs b/packages/server/src/api/controllers/static/selfhost/index.hbs deleted file mode 100644 index cbf1b8f3e5..0000000000 --- a/packages/server/src/api/controllers/static/selfhost/index.hbs +++ /dev/null @@ -1,173 +0,0 @@ - - - - - Budibase self hosting️ - - - -
- -

Get started with Budibase Self Hosting

-

Use the address in your Builder

-
-
-

πŸ“šDocumentation

-

- Find out more about your self hosted platform. -

- - Documentation - -
-
-

πŸ’»Next steps

-

- Find out how to make use of your self hosted Budibase platform. -

- - Next steps - -
-
-
- - - - - - -

A Hosting Key will also be required, this can be found in your hosting properties, info found here.

-
-
- - - \ No newline at end of file diff --git a/packages/server/src/api/controllers/static/selfhost/logo.svg b/packages/server/src/api/controllers/static/selfhost/logo.svg deleted file mode 100644 index 37bfb4ff83..0000000000 --- a/packages/server/src/api/controllers/static/selfhost/logo.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/packages/server/src/api/controllers/templates.js b/packages/server/src/api/controllers/templates.js index c3cfa28706..4d55bc5957 100644 --- a/packages/server/src/api/controllers/templates.js +++ b/packages/server/src/api/controllers/templates.js @@ -1,10 +1,5 @@ const fetch = require("node-fetch") -const { - downloadTemplate, - exportTemplateFromApp, - getLocalTemplates, -} = require("../../utilities/templates") -const env = require("../../environment") +const { downloadTemplate } = require("../../utilities/fileSystem") // development flag, can be used to test against templates exported locally const DEFAULT_TEMPLATES_BUCKET = @@ -12,16 +7,11 @@ const DEFAULT_TEMPLATES_BUCKET = exports.fetch = async function(ctx) { const { type = "app" } = ctx.query - - if (env.LOCAL_TEMPLATES) { - ctx.body = Object.values(getLocalTemplates()[type]) - } else { - const response = await fetch( - `https://${DEFAULT_TEMPLATES_BUCKET}/manifest.json` - ) - const json = await response.json() - ctx.body = Object.values(json.templates[type]) - } + const response = await fetch( + `https://${DEFAULT_TEMPLATES_BUCKET}/manifest.json` + ) + const json = await response.json() + ctx.body = Object.values(json.templates[type]) } // can't currently test this, have to ignore from coverage @@ -29,26 +19,9 @@ exports.fetch = async function(ctx) { exports.downloadTemplate = async function(ctx) { const { type, name } = ctx.params - if (!env.LOCAL_TEMPLATES) { - await downloadTemplate(type, name) - } + await downloadTemplate(type, name) ctx.body = { message: `template ${type}:${name} downloaded successfully.`, } } - -exports.exportTemplateFromApp = async function(ctx) { - const { appId } = ctx.user - const { templateName } = ctx.request.body - - await exportTemplateFromApp({ - appId, - templateName, - }) - - ctx.status = 200 - ctx.body = { - message: `Created template: ${templateName}`, - } -} diff --git a/packages/server/src/api/controllers/view/index.js b/packages/server/src/api/controllers/view/index.js index f482f3f2a6..0f6f008a1b 100644 --- a/packages/server/src/api/controllers/view/index.js +++ b/packages/server/src/api/controllers/view/index.js @@ -1,8 +1,6 @@ const CouchDB = require("../../../db") const viewTemplate = require("./viewBuilder") -const fs = require("fs") -const { join } = require("../../../utilities/centralPath") -const os = require("os") +const { apiFileReturn } = require("../../../utilities/fileSystem") const exporters = require("./exporters") const { fetchView } = require("../row") const { ViewNames } = require("../../../db/utils") @@ -120,12 +118,10 @@ const controller = { // Export part let headers = Object.keys(schema) const exporter = exporters[format] - const exportedFile = exporter(headers, ctx.body) const filename = `${viewName}.${format}` - fs.writeFileSync(join(os.tmpdir(), filename), exportedFile) - + // send down the file ctx.attachment(filename) - ctx.body = fs.createReadStream(join(os.tmpdir(), filename)) + ctx.body = apiFileReturn(exporter(headers, ctx.body)) }, } diff --git a/packages/server/src/api/index.js b/packages/server/src/api/index.js index 7628fa2077..1f0fa2383f 100644 --- a/packages/server/src/api/index.js +++ b/packages/server/src/api/index.js @@ -3,9 +3,12 @@ const authenticated = require("../middleware/authenticated") const compress = require("koa-compress") const zlib = require("zlib") const { budibaseAppsDir } = require("../utilities/budibaseDir") -const { isDev } = require("../utilities") const { mainRoutes, authRoutes, staticRoutes } = require("./routes") const pkg = require("../../package.json") +const bullboard = require("bull-board") +const expressApp = require("express")() + +expressApp.use("/bulladmin", bullboard.router) const router = new Router() const env = require("../environment") @@ -29,9 +32,13 @@ router jwtSecret: env.JWT_SECRET, useAppRootPath: true, } - ctx.isDev = isDev() await next() }) + .use("/bulladmin", ctx => { + ctx.status = 200 + ctx.respond = false + expressApp(ctx.req, ctx.res) + }) .use("/health", ctx => (ctx.status = 200)) .use("/version", ctx => (ctx.body = pkg.version)) .use(authenticated) @@ -68,8 +75,6 @@ for (let route of mainRoutes) { router.use(staticRoutes.routes()) router.use(staticRoutes.allowedMethods()) -if (!env.SELF_HOSTED && !env.CLOUD) { - router.redirect("/", "/_builder") -} +router.redirect("/", "/_builder") module.exports = router diff --git a/packages/server/src/api/routes/admin.js b/packages/server/src/api/routes/admin.js new file mode 100644 index 0000000000..540e0d7c6d --- /dev/null +++ b/packages/server/src/api/routes/admin.js @@ -0,0 +1,4 @@ +const app = require("express")() +const { router } = require("bull-board") + +app.use("/admin/queues", router) diff --git a/packages/server/src/api/routes/script.js b/packages/server/src/api/routes/script.js index e69de29bb2..48cbad84b3 100644 --- a/packages/server/src/api/routes/script.js +++ b/packages/server/src/api/routes/script.js @@ -0,0 +1,14 @@ +const Router = require("@koa/router") +const controller = require("../controllers/hosting") +const authorized = require("../../middleware/authorized") +const selfhost = require("../../middleware/selfhost") +const { BUILDER } = require("../../utilities/security/permissions") + +const router = Router() + +router + .post("/api/script", authorized(BUILDER), controller.save) + // this isn't risky, doesn't return anything about apps other than names and URLs + .get("/api/hosting/apps", selfhost, controller.getDeployedApps) + +module.exports = router diff --git a/packages/server/src/api/routes/static.js b/packages/server/src/api/routes/static.js index 30701d578b..d5d57f6d76 100644 --- a/packages/server/src/api/routes/static.js +++ b/packages/server/src/api/routes/static.js @@ -1,10 +1,10 @@ const Router = require("@koa/router") const controller = require("../controllers/static") const { budibaseTempDir } = require("../../utilities/budibaseDir") -const env = require("../../environment") const authorized = require("../../middleware/authorized") const { BUILDER } = require("../../utilities/security/permissions") const usage = require("../../middleware/usageQuota") +const env = require("../../environment") const router = Router() @@ -13,22 +13,16 @@ router.param("file", async (file, ctx, next) => { ctx.file = file && file.includes(".") ? file : "index.html" // Serving the client library from your local dir in dev - if (ctx.isDev && ctx.file.startsWith("budibase-client")) { + if (env.isDev() && ctx.file.startsWith("budibase-client")) { ctx.devPath = budibaseTempDir() } await next() }) -if (env.NODE_ENV !== "production") { - router.get("/_builder/:file*", controller.serveBuilder) -} - -if (env.SELF_HOSTED) { - router.get("/", controller.serveSelfHostPage) -} - router + // TODO: for now this _builder endpoint is not authorized/secured, will need to be + .get("/_builder/:file*", controller.serveBuilder) .post("/api/attachments/process", authorized(BUILDER), controller.uploadFile) .post("/api/attachments/upload", usage, controller.uploadFile) .get("/componentlibrary", controller.serveComponentLibrary) diff --git a/packages/server/src/api/routes/templates.js b/packages/server/src/api/routes/templates.js index 05882a22ea..6a427e8383 100644 --- a/packages/server/src/api/routes/templates.js +++ b/packages/server/src/api/routes/templates.js @@ -12,6 +12,5 @@ router authorized(BUILDER), controller.downloadTemplate ) - .post("/api/templates", authorized(BUILDER), controller.exportTemplateFromApp) module.exports = router diff --git a/packages/server/src/api/routes/tests/apikeys.spec.js b/packages/server/src/api/routes/tests/apikeys.spec.js index dbee57c8b0..24402a8794 100644 --- a/packages/server/src/api/routes/tests/apikeys.spec.js +++ b/packages/server/src/api/routes/tests/apikeys.spec.js @@ -1,8 +1,5 @@ const setup = require("./utilities") const { checkBuilderEndpoint } = require("./utilities/TestFunctions") -const { budibaseAppsDir } = require("../../../utilities/budibaseDir") -const fs = require("fs") -const path = require("path") describe("/api/keys", () => { let request = setup.getRequest() @@ -16,12 +13,14 @@ describe("/api/keys", () => { describe("fetch", () => { it("should allow fetching", async () => { - const res = await request - .get(`/api/keys`) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - expect(res.body).toBeDefined() + await setup.switchToSelfHosted(async () => { + const res = await request + .get(`/api/keys`) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body).toBeDefined() + }) }) it("should check authorization for builder", async () => { @@ -35,17 +34,18 @@ describe("/api/keys", () => { describe("update", () => { it("should allow updating a value", async () => { - fs.writeFileSync(path.join(budibaseAppsDir(), ".env"), "TEST_API_KEY=thing") - const res = await request - .put(`/api/keys/TEST`) - .send({ - value: "test" - }) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - expect(res.body["TEST"]).toEqual("test") - expect(process.env.TEST_API_KEY).toEqual("test") + await setup.switchToSelfHosted(async () => { + const res = await request + .put(`/api/keys/TEST`) + .send({ + value: "test" + }) + .set(config.defaultHeaders()) + .expect("Content-Type", /json/) + .expect(200) + expect(res.body._id).toBeDefined() + expect(res.body._rev).toBeDefined() + }) }) it("should check authorization for builder", async () => { diff --git a/packages/server/src/api/routes/tests/auth.spec.js b/packages/server/src/api/routes/tests/auth.spec.js index 0eb0b6d851..13695d596d 100644 --- a/packages/server/src/api/routes/tests/auth.spec.js +++ b/packages/server/src/api/routes/tests/auth.spec.js @@ -1,4 +1,3 @@ -const { checkBuilderEndpoint } = require("./utilities/TestFunctions") const setup = require("./utilities") describe("/authenticate", () => { diff --git a/packages/server/src/api/routes/tests/backup.spec.js b/packages/server/src/api/routes/tests/backup.spec.js index d603990294..4e586bfd08 100644 --- a/packages/server/src/api/routes/tests/backup.spec.js +++ b/packages/server/src/api/routes/tests/backup.spec.js @@ -1,6 +1,8 @@ const { checkBuilderEndpoint } = require("./utilities/TestFunctions") const setup = require("./utilities") +jest.mock("../../../utilities/fileSystem/utilities") + describe("/backups", () => { let request = setup.getRequest() let config = setup.getConfig() @@ -14,7 +16,7 @@ describe("/backups", () => { describe("exportAppDump", () => { it("should be able to export app", async () => { const res = await request - .get(`/api/backups/export?appId=${config.getAppId()}`) + .get(`/api/backups/export?appId=${config.getAppId()}&appname=test`) .set(config.defaultHeaders()) .expect(200) expect(res.text).toBeDefined() diff --git a/packages/server/src/api/routes/tests/cloud.spec.js b/packages/server/src/api/routes/tests/cloud.spec.js deleted file mode 100644 index 3cb65ed819..0000000000 --- a/packages/server/src/api/routes/tests/cloud.spec.js +++ /dev/null @@ -1,16 +0,0 @@ -const setup = require("./utilities") - -describe("test things in the Cloud/Self hosted", () => { - describe("test self hosted static page", () => { - it("should be able to load the static page", async () => { - await setup.switchToCloudForFunction(async () => { - let request = setup.getRequest() - let config = setup.getConfig() - await config.init() - const res = await request.get(`/`).expect(200) - expect(res.text.includes("Budibase self hosting️")).toEqual(true) - setup.afterAll() - }) - }) - }) -}) diff --git a/packages/server/src/api/routes/tests/component.spec.js b/packages/server/src/api/routes/tests/component.spec.js index cabf9f8223..a485939ae4 100644 --- a/packages/server/src/api/routes/tests/component.spec.js +++ b/packages/server/src/api/routes/tests/component.spec.js @@ -1,8 +1,15 @@ const { checkBuilderEndpoint } = require("./utilities/TestFunctions") const setup = require("./utilities") -const fs = require("fs") -const { resolve, join } = require("path") -const { budibaseAppsDir } = require("../../../utilities/budibaseDir") + +jest.mock("../../../utilities/fileSystem/utilities", () => ({ + ...jest.requireActual("../../../utilities/fileSystem/utilities"), + retrieve: () => { + const { join } = require("path") + const library = join("@budibase", "standard-components") + const path = require.resolve(library).split(join("dist", "index.js"))[0] + "manifest.json" + return JSON.stringify(require(path)) + } +})) describe("/component", () => { let request = setup.getRequest() @@ -14,23 +21,8 @@ describe("/component", () => { await config.init() }) - function mock() { - const manifestFile = "manifest.json" - const appId = config.getAppId() - const libraries = [join("@budibase", "standard-components")] - for (let library of libraries) { - let appDirectory = resolve(budibaseAppsDir(), appId, "node_modules", library, "package") - fs.mkdirSync(appDirectory, { recursive: true }) - - const file = require.resolve(library).split(join("dist", "index.js"))[0] + manifestFile - fs.copyFileSync(file, join(appDirectory, manifestFile)) - } - } - describe("fetch definitions", () => { it("should be able to fetch definitions", async () => { - // have to "mock" the files required - mock() const res = await request .get(`/${config.getAppId()}/components/definitions`) .set(config.defaultHeaders()) diff --git a/packages/server/src/api/routes/tests/hosting.spec.js b/packages/server/src/api/routes/tests/hosting.spec.js index 2da5b11778..99a44640bc 100644 --- a/packages/server/src/api/routes/tests/hosting.spec.js +++ b/packages/server/src/api/routes/tests/hosting.spec.js @@ -107,17 +107,16 @@ describe("/hosting", () => { }) describe("getDeployedApps", () => { - it("should get apps when in builder", async () => { - const res = await request + it("should fail when not self hosted", async () => { + await request .get(`/api/hosting/apps`) .set(config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(200) - expect(res.body.app1).toEqual({url: "/app1"}) + .expect(400) }) it("should get apps when in cloud", async () => { - await setup.switchToCloudForFunction(async () => { + await setup.switchToSelfHosted(async () => { const res = await request .get(`/api/hosting/apps`) .set(config.defaultHeaders()) diff --git a/packages/server/src/api/routes/tests/query.spec.js b/packages/server/src/api/routes/tests/query.spec.js index 87938c6a37..2755c0230e 100644 --- a/packages/server/src/api/routes/tests/query.spec.js +++ b/packages/server/src/api/routes/tests/query.spec.js @@ -89,7 +89,7 @@ describe("/queries", () => { }) it("should find a query in cloud", async () => { - await setup.switchToCloudForFunction(async () => { + await setup.switchToSelfHosted(async () => { const query = await config.createQuery() const res = await request .get(`/api/queries/${query._id}`) diff --git a/packages/server/src/api/routes/tests/row.spec.js b/packages/server/src/api/routes/tests/row.spec.js index 652a17366d..c79f648c51 100644 --- a/packages/server/src/api/routes/tests/row.spec.js +++ b/packages/server/src/api/routes/tests/row.spec.js @@ -410,7 +410,7 @@ describe("/rows", () => { tableId: table._id, }) // the environment needs configured for this - await setup.switchToCloudForFunction(async () => { + await setup.switchToSelfHosted(async () => { const enriched = await outputProcessing(config.getAppId(), table, [row]) expect(enriched[0].attachment[0].url).toBe(`/app-assets/assets/${config.getAppId()}/test/thing`) }) diff --git a/packages/server/src/api/routes/tests/templates.spec.js b/packages/server/src/api/routes/tests/templates.spec.js index f0d26bc7db..30e337e855 100644 --- a/packages/server/src/api/routes/tests/templates.spec.js +++ b/packages/server/src/api/routes/tests/templates.spec.js @@ -1,7 +1,4 @@ const setup = require("./utilities") -const { budibaseAppsDir } = require("../../../utilities/budibaseDir") -const fs = require("fs") -const { join } = require("path") describe("/templates", () => { let request = setup.getRequest() @@ -24,26 +21,4 @@ describe("/templates", () => { expect(Array.isArray(res.body)).toEqual(true) }) }) - - describe("export", () => { - it("should be able to export the basic app", async () => { - const res = await request - .post(`/api/templates`) - .send({ - templateName: "test", - }) - .set(config.defaultHeaders()) - .expect("Content-Type", /json/) - .expect(200) - expect(res.body.message).toEqual("Created template: test") - const dir = join( - budibaseAppsDir(), - "templates", - "app", - "test", - "db" - ) - expect(fs.existsSync(dir)).toEqual(true) - }) - }) }) \ No newline at end of file diff --git a/packages/server/src/api/routes/tests/utilities/index.js b/packages/server/src/api/routes/tests/utilities/index.js index ed5c98cc48..3bd3886a31 100644 --- a/packages/server/src/api/routes/tests/utilities/index.js +++ b/packages/server/src/api/routes/tests/utilities/index.js @@ -35,18 +35,18 @@ exports.getConfig = () => { return config } -exports.switchToCloudForFunction = async func => { +exports.switchToSelfHosted = async func => { // self hosted stops any attempts to Dynamo - env.CLOUD = true - env.SELF_HOSTED = true + env._set("NODE_ENV", "production") + env._set("SELF_HOSTED", true) let error try { await func() } catch (err) { error = err } - env.CLOUD = false - env.SELF_HOSTED = false + env._set("NODE_ENV", "jest") + env._set("SELF_HOSTED", false) // don't throw error until after reset if (error) { throw error diff --git a/packages/server/src/app.js b/packages/server/src/app.js index 8bbea00474..e5e9b77084 100644 --- a/packages/server/src/app.js +++ b/packages/server/src/app.js @@ -9,6 +9,7 @@ const env = require("./environment") const eventEmitter = require("./events") const automations = require("./automations/index") const Sentry = require("@sentry/node") +const fileSystem = require("./utilities/fileSystem") const app = new Koa() @@ -65,6 +66,7 @@ module.exports = server.listen(env.PORT || 0, async () => { console.log(`Budibase running on ${JSON.stringify(server.address())}`) env._set("PORT", server.address().port) eventEmitter.emitPort(env.PORT) + fileSystem.init() await automations.init() }) diff --git a/packages/server/src/automations/actions.js b/packages/server/src/automations/actions.js index ee57f5a109..03079d62e0 100644 --- a/packages/server/src/automations/actions.js +++ b/packages/server/src/automations/actions.js @@ -3,19 +3,16 @@ const createRow = require("./steps/createRow") const updateRow = require("./steps/updateRow") const deleteRow = require("./steps/deleteRow") const createUser = require("./steps/createUser") +const executeScript = require("./steps/executeScript") +const executeQuery = require("./steps/executeQuery") const outgoingWebhook = require("./steps/outgoingWebhook") const env = require("../environment") -const download = require("download") -const fetch = require("node-fetch") -const { join } = require("../utilities/centralPath") -const os = require("os") -const fs = require("fs") const Sentry = require("@sentry/node") +const { + automationInit, + getExternalAutomationStep, +} = require("../utilities/fileSystem") -const DEFAULT_BUCKET = - "https://prod-budi-automations.s3-eu-west-1.amazonaws.com" -const DEFAULT_DIRECTORY = ".budibase-automations" -const AUTOMATION_MANIFEST = "manifest.json" const BUILTIN_ACTIONS = { SEND_EMAIL: sendEmail.run, CREATE_ROW: createRow.run, @@ -23,6 +20,8 @@ const BUILTIN_ACTIONS = { DELETE_ROW: deleteRow.run, CREATE_USER: createUser.run, OUTGOING_WEBHOOK: outgoingWebhook.run, + EXECUTE_SCRIPT: executeScript.run, + EXECUTE_QUERY: executeQuery.run, } const BUILTIN_DEFINITIONS = { SEND_EMAIL: sendEmail.definition, @@ -31,10 +30,10 @@ const BUILTIN_DEFINITIONS = { DELETE_ROW: deleteRow.definition, CREATE_USER: createUser.definition, OUTGOING_WEBHOOK: outgoingWebhook.definition, + EXECUTE_SCRIPT: executeScript.definition, + EXECUTE_QUERY: executeQuery.definition, } -let AUTOMATION_BUCKET = env.AUTOMATION_BUCKET -let AUTOMATION_DIRECTORY = env.AUTOMATION_DIRECTORY let MANIFEST = null /* istanbul ignore next */ @@ -42,22 +41,13 @@ function buildBundleName(pkgName, version) { return `${pkgName}@${version}.min.js` } -/* istanbul ignore next */ -async function downloadPackage(name, version, bundleName) { - await download( - `${AUTOMATION_BUCKET}/${name}/${version}/${bundleName}`, - AUTOMATION_DIRECTORY - ) - return require(join(AUTOMATION_DIRECTORY, bundleName)) -} - /* istanbul ignore next */ module.exports.getAction = async function(actionName) { if (BUILTIN_ACTIONS[actionName] != null) { return BUILTIN_ACTIONS[actionName] } // worker pools means that a worker may not have manifest - if (env.CLOUD && MANIFEST == null) { + if (env.isProd() && MANIFEST == null) { MANIFEST = await module.exports.init() } // env setup to get async packages @@ -66,28 +56,12 @@ module.exports.getAction = async function(actionName) { } const pkg = MANIFEST.packages[actionName] const bundleName = buildBundleName(pkg.stepId, pkg.version) - try { - return require(join(AUTOMATION_DIRECTORY, bundleName)) - } catch (err) { - return downloadPackage(pkg.stepId, pkg.version, bundleName) - } + return getExternalAutomationStep(pkg.stepId, pkg.version, bundleName) } module.exports.init = async function() { - // set defaults - if (!AUTOMATION_DIRECTORY) { - AUTOMATION_DIRECTORY = join(os.homedir(), DEFAULT_DIRECTORY) - } - if (!AUTOMATION_BUCKET) { - AUTOMATION_BUCKET = DEFAULT_BUCKET - } - if (!fs.existsSync(AUTOMATION_DIRECTORY)) { - fs.mkdirSync(AUTOMATION_DIRECTORY, { recursive: true }) - } - // env setup to get async packages try { - let response = await fetch(`${AUTOMATION_BUCKET}/${AUTOMATION_MANIFEST}`) - MANIFEST = await response.json() + MANIFEST = await automationInit() module.exports.DEFINITIONS = MANIFEST && MANIFEST.packages ? Object.assign(MANIFEST.packages, BUILTIN_DEFINITIONS) diff --git a/packages/server/src/automations/index.js b/packages/server/src/automations/index.js index 9aba399133..d67227e6ac 100644 --- a/packages/server/src/automations/index.js +++ b/packages/server/src/automations/index.js @@ -34,10 +34,10 @@ module.exports.init = async function() { await actions.init() triggers.automationQueue.process(async job => { try { - if (env.CLOUD && job.data.automation && !env.SELF_HOSTED) { + if (env.USE_QUOTAS) { job.data.automation.apiKey = await updateQuota(job.data.automation) } - if (env.BUDIBASE_ENVIRONMENT === "PRODUCTION") { + if (env.isProd()) { await runWorker(job) } else { await singleThread(job) diff --git a/packages/server/src/automations/steps/createRow.js b/packages/server/src/automations/steps/createRow.js index ef136e1131..aa910dbb42 100644 --- a/packages/server/src/automations/steps/createRow.js +++ b/packages/server/src/automations/steps/createRow.js @@ -85,7 +85,7 @@ module.exports.run = async function({ inputs, appId, apiKey, emitter }) { inputs.row.tableId, inputs.row ) - if (env.CLOUD) { + if (env.isProd()) { await usage.update(apiKey, usage.Properties.ROW, 1) } await rowController.save(ctx) diff --git a/packages/server/src/automations/steps/createUser.js b/packages/server/src/automations/steps/createUser.js index 8496967105..147a3f7868 100644 --- a/packages/server/src/automations/steps/createUser.js +++ b/packages/server/src/automations/steps/createUser.js @@ -72,7 +72,7 @@ module.exports.run = async function({ inputs, appId, apiKey, emitter }) { } try { - if (env.CLOUD) { + if (env.isProd()) { await usage.update(apiKey, usage.Properties.USER, 1) } await userController.create(ctx) diff --git a/packages/server/src/automations/steps/deleteRow.js b/packages/server/src/automations/steps/deleteRow.js index ea4d60a04e..57555ddaad 100644 --- a/packages/server/src/automations/steps/deleteRow.js +++ b/packages/server/src/automations/steps/deleteRow.js @@ -70,7 +70,7 @@ module.exports.run = async function({ inputs, appId, apiKey, emitter }) { } try { - if (env.CLOUD) { + if (env.isProd()) { await usage.update(apiKey, usage.Properties.ROW, -1) } await rowController.destroy(ctx) diff --git a/packages/server/src/automations/steps/executeQuery.js b/packages/server/src/automations/steps/executeQuery.js new file mode 100644 index 0000000000..5d85cecdf7 --- /dev/null +++ b/packages/server/src/automations/steps/executeQuery.js @@ -0,0 +1,78 @@ +const queryController = require("../../api/controllers/query") + +module.exports.definition = { + name: "External Data Connector", + tagline: "Execute Data Connector", + icon: "ri-database-2-line", + description: "Execute a query in an external data connector", + type: "ACTION", + stepId: "EXECUTE_QUERY", + inputs: {}, + schema: { + inputs: { + properties: { + query: { + type: "string", + customType: "query", + title: "Query", + }, + parameters: { + title: "Query Parameters", + type: "object", + customType: "query", + }, + }, + }, + outputs: { + properties: { + response: { + type: "object", + description: "The response from the datasource execution", + }, + success: { + type: "boolean", + description: "Whether the action was successful", + }, + }, + }, + required: ["response", "success"], + }, +} + +module.exports.run = async function({ inputs, appId, emitter }) { + if (inputs.query == null) { + return { + success: false, + response: { + message: "Invalid inputs", + }, + } + } + const ctx = { + params: { + queryId: inputs.query, + }, + request: { + body: { + parameters: inputs.parameters, + }, + }, + user: { appId }, + eventEmitter: emitter, + } + + await queryController.execute(ctx) + + try { + return { + response: ctx.body, + success: ctx.status === 200, + } + } catch (err) { + console.error(err) + return { + success: false, + response: err, + } + } +} diff --git a/packages/server/src/automations/steps/scripting.js b/packages/server/src/automations/steps/executeScript.js similarity index 54% rename from packages/server/src/automations/steps/scripting.js rename to packages/server/src/automations/steps/executeScript.js index fd529b8848..b08ab180af 100644 --- a/packages/server/src/automations/steps/scripting.js +++ b/packages/server/src/automations/steps/executeScript.js @@ -1,6 +1,4 @@ const scriptController = require("../../api/controllers/script") -const automationUtils = require("../automationUtils") -const env = require("../../environment") module.exports.definition = { name: "Scripting", @@ -14,7 +12,8 @@ module.exports.definition = { inputs: { properties: { code: { - type: "code", + type: "string", + customType: "code", title: "Code", }, }, @@ -22,34 +21,17 @@ module.exports.definition = { }, outputs: { properties: { - row: { - type: "object", - customType: "row", - description: "The new row", - }, - response: { - type: "object", - description: "The response from the table", - }, success: { type: "boolean", description: "Whether the action was successful", }, - id: { - type: "string", - description: "The identifier of the new row", - }, - revision: { - type: "string", - description: "The revision of the new row", - }, }, - required: ["success", "id", "revision"], }, + required: ["success"], }, } -module.exports.run = async function({ inputs, appId, emitter }) { +module.exports.run = async function({ inputs, appId, context, emitter }) { if (inputs.code == null) { return { success: false, @@ -60,12 +42,10 @@ module.exports.run = async function({ inputs, appId, emitter }) { } const ctx = { - params: { - tableId: inputs.row.tableId, - }, request: { body: { - script: inputs.script, + script: inputs.code, + context, }, }, user: { appId }, @@ -73,13 +53,8 @@ module.exports.run = async function({ inputs, appId, emitter }) { } try { - // inputs.row = await automationUtils.cleanUpRow(appId, inputs.script) await scriptController.execute(ctx) return { - // row: inputs.row, - response: ctx.body, - // id: ctx.body._id, - // revision: ctx.body._rev, success: ctx.status === 200, } } catch (err) { diff --git a/packages/server/src/automations/tests/automation.spec.js b/packages/server/src/automations/tests/automation.spec.js index f4d3b4c865..2e9bb16e55 100644 --- a/packages/server/src/automations/tests/automation.spec.js +++ b/packages/server/src/automations/tests/automation.spec.js @@ -47,27 +47,23 @@ describe("Run through some parts of the automations system", () => { expect(thread).toHaveBeenCalled() }) - it("should be able to init in cloud", async () => { - env.CLOUD = true - env.BUDIBASE_ENVIRONMENT = "PRODUCTION" - await triggers.externalTrigger(basicAutomation(), { a: 1 }) - await wait(100) - // haven't added a mock implementation so getAPIKey of usageQuota just returns undefined - expect(usageQuota.update).toHaveBeenCalledWith("test", "automationRuns", 1) - expect(workerJob).toBeDefined() - env.BUDIBASE_ENVIRONMENT = "JEST" - env.CLOUD = false + it("should be able to init in prod", async () => { + await setup.runInProd(async () => { + await triggers.externalTrigger(basicAutomation(), { a: 1 }) + await wait(100) + // haven't added a mock implementation so getAPIKey of usageQuota just returns undefined + expect(usageQuota.update).toHaveBeenCalledWith("test", "automationRuns", 1) + expect(workerJob).toBeDefined() + }) }) it("try error scenario", async () => { - env.CLOUD = true - env.BUDIBASE_ENVIRONMENT = "PRODUCTION" - // the second call will throw an error - await triggers.externalTrigger(basicAutomation(), { a: 1 }) - await wait(100) - expect(console.error).toHaveBeenCalled() - env.BUDIBASE_ENVIRONMENT = "JEST" - env.CLOUD = false + await setup.runInProd(async () => { + // the second call will throw an error + await triggers.externalTrigger(basicAutomation(), { a: 1 }) + await wait(100) + expect(console.error).toHaveBeenCalled() + }) }) it("should be able to check triggering row filling", async () => { diff --git a/packages/server/src/automations/tests/createRow.spec.js b/packages/server/src/automations/tests/createRow.spec.js index 0be2803e47..c01d630bed 100644 --- a/packages/server/src/automations/tests/createRow.spec.js +++ b/packages/server/src/automations/tests/createRow.spec.js @@ -42,12 +42,12 @@ describe("test the create row action", () => { }) it("check usage quota attempts", async () => { - env.CLOUD = true - await setup.runStep(setup.actions.CREATE_ROW.stepId, { - row + await setup.runInProd(async () => { + await setup.runStep(setup.actions.CREATE_ROW.stepId, { + row + }) + expect(usageQuota.update).toHaveBeenCalledWith(setup.apiKey, "rows", 1) }) - expect(usageQuota.update).toHaveBeenCalledWith(setup.apiKey, "rows", 1) - env.CLOUD = false }) it("should check invalid inputs return an error", async () => { diff --git a/packages/server/src/automations/tests/createUser.spec.js b/packages/server/src/automations/tests/createUser.spec.js index 5f65e260a9..f188c31aa4 100644 --- a/packages/server/src/automations/tests/createUser.spec.js +++ b/packages/server/src/automations/tests/createUser.spec.js @@ -35,9 +35,9 @@ describe("test the create user action", () => { }) it("check usage quota attempts", async () => { - env.CLOUD = true - await setup.runStep(setup.actions.CREATE_USER.stepId, user) - expect(usageQuota.update).toHaveBeenCalledWith(setup.apiKey, "users", 1) - env.CLOUD = false + await setup.runInProd(async () => { + await setup.runStep(setup.actions.CREATE_USER.stepId, user) + expect(usageQuota.update).toHaveBeenCalledWith(setup.apiKey, "users", 1) + }) }) }) diff --git a/packages/server/src/automations/tests/deleteRow.spec.js b/packages/server/src/automations/tests/deleteRow.spec.js index 0d5ff47ed8..2a300cbd8c 100644 --- a/packages/server/src/automations/tests/deleteRow.spec.js +++ b/packages/server/src/automations/tests/deleteRow.spec.js @@ -36,10 +36,10 @@ describe("test the delete row action", () => { }) it("check usage quota attempts", async () => { - env.CLOUD = true - await setup.runStep(setup.actions.DELETE_ROW.stepId, inputs) - expect(usageQuota.update).toHaveBeenCalledWith(setup.apiKey, "rows", -1) - env.CLOUD = false + await setup.runInProd(async () => { + await setup.runStep(setup.actions.DELETE_ROW.stepId, inputs) + expect(usageQuota.update).toHaveBeenCalledWith(setup.apiKey, "rows", -1) + }) }) it("should check invalid inputs return an error", async () => { diff --git a/packages/server/src/automations/tests/utilities/index.js b/packages/server/src/automations/tests/utilities/index.js index ad149d6bde..ab9de55430 100644 --- a/packages/server/src/automations/tests/utilities/index.js +++ b/packages/server/src/automations/tests/utilities/index.js @@ -2,6 +2,7 @@ const TestConfig = require("../../../tests/utilities/TestConfiguration") const actions = require("../../actions") const logic = require("../../logic") const emitter = require("../../../events/index") +const env = require("../../../environment") let config @@ -16,6 +17,22 @@ exports.afterAll = () => { config.end() } +exports.runInProd = async fn => { + env._set("NODE_ENV", "production") + env._set("USE_QUOTAS", 1) + let error + try { + await fn() + } catch (err) { + error = err + } + env._set("NODE_ENV", "jest") + env._set("USE_QUOTAS", null) + if (error) { + throw error + } +} + exports.runStep = async function runStep(stepId, inputs) { let step if ( diff --git a/packages/server/src/automations/thread.js b/packages/server/src/automations/thread.js index 7dbf9272ec..d4c89ac349 100644 --- a/packages/server/src/automations/thread.js +++ b/packages/server/src/automations/thread.js @@ -56,6 +56,7 @@ class Orchestrator { appId: this._appId, apiKey: automation.apiKey, emitter: this._emitter, + context: this._context, }) if (step.stepId === FILTER_STEP_ID && !outputs.success) { break diff --git a/packages/server/src/automations/triggers.js b/packages/server/src/automations/triggers.js index 7e50e5ee74..e274aed946 100644 --- a/packages/server/src/automations/triggers.js +++ b/packages/server/src/automations/triggers.js @@ -1,10 +1,15 @@ const CouchDB = require("../db") const emitter = require("../events/index") -const InMemoryQueue = require("../utilities/queue/inMemoryQueue") +// const InMemoryQueue = require("../utilities/queue/inMemoryQueue") +const Queue = require("bull") +const { setQueues, BullAdapter } = require("bull-board") const { getAutomationParams } = require("../db/utils") const { coerce } = require("../utilities/rowProcessor") -let automationQueue = new InMemoryQueue("automationQueue") +let automationQueue = new Queue("automationQueue") + +// Set up queues for bull board admin +setQueues([new BullAdapter(automationQueue)]) const FAKE_STRING = "TEST" const FAKE_BOOL = false diff --git a/packages/server/src/constants/index.js b/packages/server/src/constants/index.js index 46fb5cb649..379cfb8aa5 100644 --- a/packages/server/src/constants/index.js +++ b/packages/server/src/constants/index.js @@ -80,8 +80,6 @@ exports.AutoFieldSubTypes = { AUTO_ID: "autoID", } -exports.BUILDER_CONFIG_DB = "builder-config-db" -exports.HOSTING_DOC = "hosting-doc" exports.OBJ_STORE_DIRECTORY = "/app-assets/assets" exports.BaseQueryVerbs = { CREATE: "create", @@ -89,3 +87,9 @@ exports.BaseQueryVerbs = { UPDATE: "update", DELETE: "delete", } + +exports.ObjectStoreBuckets = { + BACKUPS: "backups", + APPS: "prod-budi-app-assets", + TEMPLATES: "templates", +} diff --git a/packages/server/src/db/builder.js b/packages/server/src/db/builder.js new file mode 100644 index 0000000000..d2bbcd404b --- /dev/null +++ b/packages/server/src/db/builder.js @@ -0,0 +1,38 @@ +const CouchDB = require("./index") +const { StaticDatabases } = require("./utils") +const env = require("../environment") + +const SELF_HOST_ERR = "Unable to access builder DB/doc - not self hosted." +const BUILDER_DB = StaticDatabases.BUILDER + +/** + * This is the builder database, right now this is a single, static database + * that is present across the whole system and determines some core functionality + * for the builder (e.g. storage of API keys). This has been limited to self hosting + * as it doesn't make as much sense against the currently design Cloud system. + */ + +exports.getBuilderMainDoc = async () => { + if (!env.SELF_HOSTED) { + throw SELF_HOST_ERR + } + const db = new CouchDB(BUILDER_DB.name) + try { + return await db.get(BUILDER_DB.baseDoc) + } catch (err) { + // doesn't exist yet, nothing to get + return { + _id: BUILDER_DB.baseDoc, + } + } +} + +exports.setBuilderMainDoc = async doc => { + if (!env.SELF_HOSTED) { + throw SELF_HOST_ERR + } + // make sure to override the ID + doc._id = BUILDER_DB.baseDoc + const db = new CouchDB(BUILDER_DB.name) + return db.put(doc) +} diff --git a/packages/server/src/db/client.js b/packages/server/src/db/client.js index f6dea33a40..3e3a4f50fe 100644 --- a/packages/server/src/db/client.js +++ b/packages/server/src/db/client.js @@ -1,12 +1,10 @@ const PouchDB = require("pouchdb") const replicationStream = require("pouchdb-replication-stream") const allDbs = require("pouchdb-all-dbs") -const { budibaseAppsDir } = require("../utilities/budibaseDir") const find = require("pouchdb-find") const env = require("../environment") -const COUCH_DB_URL = env.COUCH_DB_URL || `leveldb://${budibaseAppsDir()}/.data/` -const isInMemory = env.NODE_ENV === "jest" +const COUCH_DB_URL = env.COUCH_DB_URL || "http://localhost:10000/db/" PouchDB.plugin(replicationStream.plugin) PouchDB.plugin(find) @@ -14,10 +12,10 @@ PouchDB.adapter("writableStream", replicationStream.adapters.writableStream) let POUCH_DB_DEFAULTS = { prefix: COUCH_DB_URL, - skip_setup: !!env.CLOUD, + skip_setup: env.isProd(), } -if (isInMemory) { +if (env.isTest()) { PouchDB.plugin(require("pouchdb-adapter-memory")) POUCH_DB_DEFAULTS = { prefix: undefined, diff --git a/packages/server/src/db/dynamoClient.js b/packages/server/src/db/dynamoClient.js index fcba726f84..19924b1a7e 100644 --- a/packages/server/src/db/dynamoClient.js +++ b/packages/server/src/db/dynamoClient.js @@ -1,4 +1,4 @@ -let _ = require("lodash") +let { merge } = require("lodash") let env = require("../environment") const AWS_REGION = env.AWS_REGION ? env.AWS_REGION : "eu-west-1" @@ -38,7 +38,7 @@ class Table { params.Key[this._sort] = sort } if (otherProps) { - params = _.merge(params, otherProps) + params = merge(params, otherProps) } let response = await docClient.get(params).promise() return response.Item @@ -77,7 +77,7 @@ class Table { params.ConditionExpression += "attribute_exists(#PRIMARY)" } if (otherProps) { - params = _.merge(params, otherProps) + params = merge(params, otherProps) } return docClient.update(params).promise() } @@ -94,7 +94,7 @@ class Table { Item: item, } if (otherProps) { - params = _.merge(params, otherProps) + params = merge(params, otherProps) } return docClient.put(params).promise() } @@ -119,7 +119,7 @@ exports.init = endpoint => { exports.apiKeyTable = new Table(TableInfo.API_KEYS) exports.userTable = new Table(TableInfo.USERS) -if (env.CLOUD) { +if (env.isProd()) { exports.init(`https://dynamodb.${AWS_REGION}.amazonaws.com`) } else { env._set("AWS_ACCESS_KEY_ID", "KEY_ID") diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js index 2d0722d83a..e480d4f554 100644 --- a/packages/server/src/db/utils.js +++ b/packages/server/src/db/utils.js @@ -3,6 +3,18 @@ const newid = require("./newid") const UNICODE_MAX = "\ufff0" const SEPARATOR = "_" +const StaticDatabases = { + BUILDER: { + name: "builder-db", + baseDoc: "builder-doc", + }, + // TODO: needs removed + BUILDER_HOSTING: { + name: "builder-config-db", + baseDoc: "hosting-doc", + }, +} + const DocumentTypes = { TABLE: "ta", ROW: "ro", @@ -25,6 +37,7 @@ const ViewNames = { USERS: "ta_users", } +exports.StaticDatabases = StaticDatabases exports.ViewNames = ViewNames exports.DocumentTypes = DocumentTypes exports.SEPARATOR = SEPARATOR diff --git a/packages/server/src/environment.js b/packages/server/src/environment.js index 4faaabe6ab..dc15bc8a9a 100644 --- a/packages/server/src/environment.js +++ b/packages/server/src/environment.js @@ -1,45 +1,64 @@ -const { resolve, join } = require("./utilities/centralPath") -const { homedir } = require("os") -const { app } = require("electron") +function isTest() { + return ( + process.env.NODE_ENV === "jest" || + process.env.NODE_ENV === "cypress" || + process.env.JEST_WORKER_ID != null + ) +} + +function isDev() { + return ( + process.env.NODE_ENV !== "production" && + process.env.BUDIBASE_ENVIRONMENT !== "production" + ) +} let LOADED = false - -if (!LOADED) { - const homeDir = app ? app.getPath("home") : homedir() - const budibaseDir = join(homeDir, ".budibase") - process.env.BUDIBASE_DIR = budibaseDir - require("dotenv").config({ path: resolve(budibaseDir, ".env") }) +if (!LOADED && isDev() && !isTest()) { + require("dotenv").config() LOADED = true } module.exports = { - CLIENT_ID: process.env.CLIENT_ID, - NODE_ENV: process.env.NODE_ENV, - JWT_SECRET: process.env.JWT_SECRET, - BUDIBASE_DIR: process.env.BUDIBASE_DIR, + // important PORT: process.env.PORT, + JWT_SECRET: process.env.JWT_SECRET, COUCH_DB_URL: process.env.COUCH_DB_URL, + MINIO_URL: process.env.MINIO_URL, + WORKER_URL: process.env.WORKER_URL, + SELF_HOSTED: process.env.SELF_HOSTED, + AWS_REGION: process.env.AWS_REGION, + ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS, + MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY, + MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY, + USE_QUOTAS: process.env.USE_QUOTAS, + // environment + NODE_ENV: process.env.NODE_ENV, + JEST_WORKER_ID: process.env.JEST_WORKER_ID, + BUDIBASE_ENVIRONMENT: process.env.BUDIBASE_ENVIRONMENT, + // minor SALT_ROUNDS: process.env.SALT_ROUNDS, LOGGER: process.env.LOGGER, LOG_LEVEL: process.env.LOG_LEVEL, AUTOMATION_DIRECTORY: process.env.AUTOMATION_DIRECTORY, AUTOMATION_BUCKET: process.env.AUTOMATION_BUCKET, - BUDIBASE_ENVIRONMENT: process.env.BUDIBASE_ENVIRONMENT, SENDGRID_API_KEY: process.env.SENDGRID_API_KEY, - CLOUD: process.env.CLOUD, - SELF_HOSTED: process.env.SELF_HOSTED, - WORKER_URL: process.env.WORKER_URL, - HOSTING_KEY: process.env.HOSTING_KEY, DYNAMO_ENDPOINT: process.env.DYNAMO_ENDPOINT, - AWS_REGION: process.env.AWS_REGION, - DEPLOYMENT_CREDENTIALS_URL: process.env.DEPLOYMENT_CREDENTIALS_URL, + // old - to remove + CLIENT_ID: process.env.CLIENT_ID, + BUDIBASE_DIR: process.env.BUDIBASE_DIR, + DEPLOYMENT_DB_URL: process.env.DEPLOYMENT_DB_URL, BUDIBASE_API_KEY: process.env.BUDIBASE_API_KEY, USERID_API_KEY: process.env.USERID_API_KEY, - ENABLE_ANALYTICS: process.env.ENABLE_ANALYTICS, - DEPLOYMENT_DB_URL: process.env.DEPLOYMENT_DB_URL, - LOCAL_TEMPLATES: process.env.LOCAL_TEMPLATES, + DEPLOYMENT_CREDENTIALS_URL: process.env.DEPLOYMENT_CREDENTIALS_URL, + HOSTING_KEY: process.env.HOSTING_KEY, _set(key, value) { process.env[key] = value module.exports[key] = value }, + isTest, + isDev, + isProd: () => { + return !isDev() + }, } diff --git a/packages/server/src/index.js b/packages/server/src/index.js index f5506aa2a3..3aa210f96c 100644 --- a/packages/server/src/index.js +++ b/packages/server/src/index.js @@ -1,17 +1,10 @@ -const { budibaseTempDir } = require("./utilities/budibaseDir") -const { isDev } = require("./utilities") - const fixPath = require("fix-path") -const fs = require("fs") - -async function runServer() { - if (isDev() && !fs.existsSync(budibaseTempDir())) { - console.error( - "Please run a build before attempting to run server independently to fill 'tmp' directory." - ) - process.exit(-1) - } +const { checkDevelopmentEnvironment } = require("./utilities/fileSystem") +function runServer() { + // this will shutdown the system if development environment not ready + // will print an error explaining what to do + checkDevelopmentEnvironment() fixPath() require("./app") } diff --git a/packages/server/src/middleware/authenticated.js b/packages/server/src/middleware/authenticated.js index 32ed3f63d0..38d50858f1 100644 --- a/packages/server/src/middleware/authenticated.js +++ b/packages/server/src/middleware/authenticated.js @@ -59,7 +59,6 @@ module.exports = async (ctx, next) => { role: await getRole(appId, jwtPayload.roleId), } } catch (err) { - console.log(err) if (authType === AuthTypes.BUILDER) { clearCookie(ctx) ctx.status = 200 diff --git a/packages/server/src/middleware/authorized.js b/packages/server/src/middleware/authorized.js index 2a1caef2a2..564896080e 100644 --- a/packages/server/src/middleware/authorized.js +++ b/packages/server/src/middleware/authorized.js @@ -13,19 +13,12 @@ const { AuthTypes } = require("../constants") const ADMIN_ROLES = [BUILTIN_ROLE_IDS.ADMIN, BUILTIN_ROLE_IDS.BUILDER] -const LOCAL_PASS = new RegExp(["webhooks/trigger"].join("|")) - function hasResource(ctx) { return ctx.resourceId != null } module.exports = (permType, permLevel = null) => async (ctx, next) => { - // webhooks can pass locally - if (!env.CLOUD && LOCAL_PASS.test(ctx.request.url)) { - return next() - } - - if (env.CLOUD && ctx.headers["x-api-key"] && ctx.headers["x-instanceid"]) { + if (env.isProd() && ctx.headers["x-api-key"] && ctx.headers["x-instanceid"]) { // api key header passed by external webhook if (await isAPIKeyValid(ctx.headers["x-api-key"])) { ctx.auth = { @@ -41,20 +34,23 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => { return ctx.throw(403, "API key invalid") } - // don't expose builder endpoints in the cloud - if (env.CLOUD && permType === PermissionTypes.BUILDER) return - if (!ctx.user) { return ctx.throw(403, "No user info found") } const role = ctx.user.role + const isBuilder = role._id === BUILTIN_ROLE_IDS.BUILDER + const isAdmin = ADMIN_ROLES.includes(role._id) + const isAuthed = ctx.auth.authenticated + + if (permType === PermissionTypes.BUILDER && isBuilder) { + return next() + } + const { basePermissions, permissions } = await getUserPermissions( ctx.appId, role._id ) - const isAdmin = ADMIN_ROLES.includes(role._id) - const isAuthed = ctx.auth.authenticated // this may need to change in the future, right now only admins // can have access to builder features, this is hard coded into diff --git a/packages/server/src/middleware/selfhost.js b/packages/server/src/middleware/selfhost.js index 1c96cee33c..1e7117c83d 100644 --- a/packages/server/src/middleware/selfhost.js +++ b/packages/server/src/middleware/selfhost.js @@ -1,14 +1,8 @@ const env = require("../environment") -const hosting = require("../utilities/builder/hosting") // if added as a middleware will stop requests unless builder is in self host mode // or cloud is in self host module.exports = async (ctx, next) => { - if (env.CLOUD && env.SELF_HOSTED) { - await next() - return - } - const hostingInfo = await hosting.getHostingInfo() - if (hostingInfo.type === hosting.HostingTypes.SELF) { + if (env.SELF_HOSTED) { await next() return } diff --git a/packages/server/src/middleware/tests/authorized.spec.js b/packages/server/src/middleware/tests/authorized.spec.js index d3e5e52d2d..7968a8a939 100644 --- a/packages/server/src/middleware/tests/authorized.spec.js +++ b/packages/server/src/middleware/tests/authorized.spec.js @@ -3,8 +3,15 @@ const env = require("../../environment") const apiKey = require("../../utilities/security/apikey") const { AuthTypes } = require("../../constants") const { PermissionTypes, PermissionLevels } = require("../../utilities/security/permissions") -const { Test } = require("supertest") -jest.mock("../../environment") +jest.mock("../../environment", () => ({ + prod: false, + isTest: () => true, + isProd: () => this.prod, + _set: (key, value) => { + this.prod = value === "production" + } + }) +) jest.mock("../../utilities/security/apikey") class TestConfiguration { @@ -47,8 +54,8 @@ class TestConfiguration { this.ctx.request.url = url } - setCloudEnv(isCloud) { - env.CLOUD = isCloud + setEnvironment(isProd) { + env._set("NODE_ENV", isProd ? "production" : "jest") } setRequestHeaders(headers) { @@ -71,12 +78,6 @@ describe("Authorization middleware", () => { beforeEach(() => { config = new TestConfiguration() - }) - - it("passes the middleware for local webhooks", async () => { - config.setRequestUrl("https://something/webhooks/trigger") - await config.executeMiddleware() - expect(config.next).toHaveBeenCalled() }) describe("external web hook call", () => { @@ -85,7 +86,7 @@ describe("Authorization middleware", () => { beforeEach(() => { config = new TestConfiguration() - config.setCloudEnv(true) + config.setEnvironment(true) config.setRequestHeaders({ "x-api-key": "abc123", "x-instanceid": "instance123", @@ -121,7 +122,7 @@ describe("Authorization middleware", () => { beforeEach(() => { config = new TestConfiguration() - config.setCloudEnv(true) + config.setEnvironment(true) config.setAuthenticated(true) }) @@ -144,7 +145,7 @@ describe("Authorization middleware", () => { }) it("throws if the user has only builder permissions", async () => { - config.setCloudEnv(false) + config.setEnvironment(false) config.setMiddlewareRequiredPermission(PermissionTypes.BUILDER) config.setUser({ role: { diff --git a/packages/server/src/middleware/tests/selfhost.spec.js b/packages/server/src/middleware/tests/selfhost.spec.js index 061da17f9c..6ce61c60ef 100644 --- a/packages/server/src/middleware/tests/selfhost.spec.js +++ b/packages/server/src/middleware/tests/selfhost.spec.js @@ -1,6 +1,5 @@ -const selfHostMiddleware = require("../selfhost"); +const selfHostMiddleware = require("../selfhost") const env = require("../../environment") -const hosting = require("../../utilities/builder/hosting"); jest.mock("../../environment") jest.mock("../../utilities/builder/hosting") @@ -20,16 +19,6 @@ class TestConfiguration { return this.middleware(this.ctx, this.next) } - setCloudHosted() { - env.CLOUD = 1 - env.SELF_HOSTED = 0 - } - - setSelfHosted() { - env.CLOUD = 0 - env.SELF_HOSTED = 1 - } - afterEach() { jest.clearAllMocks() } @@ -46,30 +35,10 @@ describe("Self host middleware", () => { config.afterEach() }) - it("calls next() when CLOUD and SELF_HOSTED env vars are set", async () => { - env.CLOUD = 1 + it("calls next() when SELF_HOSTED env var is set", async () => { env.SELF_HOSTED = 1 await config.executeMiddleware() expect(config.next).toHaveBeenCalled() }) - - it("throws when hostingInfo type is cloud", async () => { - config.setSelfHosted() - - hosting.getHostingInfo.mockImplementationOnce(() => ({ type: hosting.HostingTypes.CLOUD })) - - await config.executeMiddleware() - expect(config.throw).toHaveBeenCalledWith(400, "Endpoint unavailable in cloud hosting.") - expect(config.next).not.toHaveBeenCalled() - }) - - it("calls the self hosting middleware to pass through to next() when the hostingInfo type is self", async () => { - config.setSelfHosted() - - hosting.getHostingInfo.mockImplementationOnce(() => ({ type: hosting.HostingTypes.SELF })) - - await config.executeMiddleware() - expect(config.next).toHaveBeenCalled() - }) }) diff --git a/packages/server/src/middleware/tests/usageQuota.spec.js b/packages/server/src/middleware/tests/usageQuota.spec.js index 395f14c1ed..9ab17ef992 100644 --- a/packages/server/src/middleware/tests/usageQuota.spec.js +++ b/packages/server/src/middleware/tests/usageQuota.spec.js @@ -5,7 +5,12 @@ const env = require("../../environment") jest.mock("../../db") jest.mock("../../utilities/usageQuota") -jest.mock("../../environment") +jest.mock("../../environment", () => ({ + isTest: () => true, + isProd: () => false, + isDev: () => true, + _set: () => {}, +})) class TestConfiguration { constructor() { @@ -32,12 +37,14 @@ class TestConfiguration { return this.middleware(this.ctx, this.next) } - cloudHosted(bool) { + setProd(bool) { if (bool) { - env.CLOUD = 1 + env.isDev = () => false + env.isProd = () => true this.ctx.auth = { apiKey: "test" } } else { - env.CLOUD = 0 + env.isDev = () => true + env.isProd = () => false } } @@ -102,7 +109,7 @@ describe("usageQuota middleware", () => { it("calculates and persists the correct usage quota for the relevant action", async () => { config.setUrl("/rows") - config.cloudHosted(true) + config.setProd(true) await config.executeMiddleware() @@ -112,7 +119,7 @@ describe("usageQuota middleware", () => { it("calculates the correct file size from a file upload call and adds it to quota", async () => { config.setUrl("/upload") - config.cloudHosted(true) + config.setProd(true) config.setFiles([ { size: 100 diff --git a/packages/server/src/middleware/usageQuota.js b/packages/server/src/middleware/usageQuota.js index 1b809868be..1bc829fbcf 100644 --- a/packages/server/src/middleware/usageQuota.js +++ b/packages/server/src/middleware/usageQuota.js @@ -44,8 +44,8 @@ module.exports = async (ctx, next) => { } } - // if running in builder or a self hosted cloud usage quotas should not be executed - if (!env.CLOUD || env.SELF_HOSTED) { + // if in development or a self hosted cloud usage quotas should not be executed + if (env.isDev() || env.SELF_HOSTED) { return next() } // update usage for uploads to be the total size diff --git a/packages/server/src/tests/utilities/TestConfiguration.js b/packages/server/src/tests/utilities/TestConfiguration.js index a12d596534..a9723c8671 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.js +++ b/packages/server/src/tests/utilities/TestConfiguration.js @@ -14,9 +14,6 @@ const { } = require("./structures") const controllers = require("./controllers") const supertest = require("supertest") -const fs = require("fs") -const { budibaseAppsDir } = require("../../utilities/budibaseDir") -const { join } = require("path") const EMAIL = "babs@babs.com" const PASSWORD = "babs_password" @@ -66,13 +63,6 @@ class TestConfiguration { if (this.server) { this.server.close() } - const appDir = budibaseAppsDir() - const files = fs.readdirSync(appDir) - for (let file of files) { - if (this.allApps.some(app => file.includes(app._id))) { - fs.rmdirSync(join(appDir, file), { recursive: true }) - } - } } defaultHeaders() { @@ -81,9 +71,11 @@ class TestConfiguration { roleId: BUILTIN_ROLE_IDS.BUILDER, } const builderToken = jwt.sign(builderUser, env.JWT_SECRET) + // can be "production" for test case + const type = env.isProd() ? "cloud" : "local" const headers = { Accept: "application/json", - Cookie: [`budibase:builder:local=${builderToken}`], + Cookie: [`budibase:builder:${type}=${builderToken}`], } if (this.appId) { headers["x-budibase-app-id"] = this.appId diff --git a/packages/server/src/utilities/builder/compileStaticAssets.js b/packages/server/src/utilities/builder/compileStaticAssets.js deleted file mode 100644 index 0389c920ee..0000000000 --- a/packages/server/src/utilities/builder/compileStaticAssets.js +++ /dev/null @@ -1,35 +0,0 @@ -const { ensureDir, constants, copyFile } = require("fs-extra") -const { join } = require("../centralPath") -const { budibaseAppsDir } = require("../budibaseDir") - -/** - * Compile all the non-db static web assets that are required for the running of - * a budibase application. This includes the JSON structure of the DOM and - * the client library, a script responsible for reading the JSON structure - * and rendering the application. - * @param {string} appId id of the application we want to compile static assets for - */ -module.exports = async appId => { - const publicPath = join(budibaseAppsDir(), appId, "public") - await ensureDir(publicPath) - await copyClientLib(publicPath) -} - -/** - * Copy the budibase client library and sourcemap from NPM to /public/. - * The client library is then served as a static asset when the budibase application - * is running in preview or prod - * @param {String} publicPath - path to write the client library to - */ -const copyClientLib = async publicPath => { - const sourcepath = require.resolve("@budibase/client") - const destPath = join(publicPath, "budibase-client.js") - - await copyFile(sourcepath, destPath, constants.COPYFILE_FICLONE) - - await copyFile( - sourcepath + ".map", - destPath + ".map", - constants.COPYFILE_FICLONE - ) -} diff --git a/packages/server/src/utilities/builder/hosting.js b/packages/server/src/utilities/builder/hosting.js index c265c26dd0..eeaf220c64 100644 --- a/packages/server/src/utilities/builder/hosting.js +++ b/packages/server/src/utilities/builder/hosting.js @@ -1,5 +1,5 @@ const CouchDB = require("../../db") -const { BUILDER_CONFIG_DB, HOSTING_DOC } = require("../../constants") +const { StaticDatabases } = require("../../db/utils") const fetch = require("node-fetch") const env = require("../../environment") @@ -23,16 +23,16 @@ exports.HostingTypes = { } exports.getHostingInfo = async () => { - const db = new CouchDB(BUILDER_CONFIG_DB) + const db = new CouchDB(StaticDatabases.BUILDER_HOSTING.name) let doc try { - doc = await db.get(HOSTING_DOC) + doc = await db.get(StaticDatabases.BUILDER_HOSTING.baseDoc) } catch (err) { // don't write this doc, want to be able to update these default props // for our servers with a new release without needing to worry about state of // PouchDB in peoples installations doc = { - _id: HOSTING_DOC, + _id: StaticDatabases.BUILDER_HOSTING.baseDoc, type: exports.HostingTypes.CLOUD, hostingUrl: PROD_HOSTING_URL, selfHostKey: "", @@ -85,15 +85,11 @@ exports.getTemplatesUrl = async (appId, type, name) => { } exports.getDeployedApps = async () => { - const hostingInfo = await exports.getHostingInfo() - if ( - (!env.CLOUD && hostingInfo.type === exports.HostingTypes.CLOUD) || - (env.CLOUD && !env.SELF_HOSTED) - ) { + if (!env.SELF_HOSTED) { throw "Can only check apps for self hosted environments" } - const workerUrl = !env.CLOUD ? await exports.getWorkerUrl() : env.WORKER_URL - const hostingKey = !env.CLOUD ? hostingInfo.selfHostKey : env.HOSTING_KEY + const workerUrl = env.WORKER_URL + const hostingKey = env.HOSTING_KEY try { const response = await fetch(`${workerUrl}/api/apps`, { method: "GET", diff --git a/packages/server/src/utilities/createAppPackage.js b/packages/server/src/utilities/createAppPackage.js deleted file mode 100644 index 9500554227..0000000000 --- a/packages/server/src/utilities/createAppPackage.js +++ /dev/null @@ -1,29 +0,0 @@ -const stream = require("stream") -const fetch = require("node-fetch") -const tar = require("tar-fs") -const zlib = require("zlib") -const { promisify } = require("util") -const packageJson = require("../../package.json") - -const streamPipeline = promisify(stream.pipeline) - -// can't really test this due to the downloading nature of it, wouldn't be a great test case -/* istanbul ignore next */ -exports.downloadExtractComponentLibraries = async appFolder => { - const LIBRARIES = ["standard-components"] - - // Need to download tarballs directly from NPM as our users may not have node on their machine - for (let lib of LIBRARIES) { - // download tarball - const registryUrl = `https://registry.npmjs.org/@budibase/${lib}/-/${lib}-${packageJson.version}.tgz` - const response = await fetch(registryUrl) - if (!response.ok) - throw new Error(`unexpected response ${response.statusText}`) - - await streamPipeline( - response.body, - zlib.Unzip(), - tar.extract(`${appFolder}/node_modules/@budibase/${lib}`) - ) - } -} diff --git a/packages/server/src/utilities/fileSystem/index.js b/packages/server/src/utilities/fileSystem/index.js new file mode 100644 index 0000000000..a64a24a9fc --- /dev/null +++ b/packages/server/src/utilities/fileSystem/index.js @@ -0,0 +1,204 @@ +const { budibaseTempDir } = require("../budibaseDir") +const { isDev } = require("../index") +const fs = require("fs") +const { join } = require("path") +const uuid = require("uuid/v4") +const CouchDB = require("../../db") +const { ObjectStoreBuckets } = require("../../constants") +const { + upload, + retrieve, + streamUpload, + deleteFolder, + downloadTarball, +} = require("./utilities") +const { downloadLibraries, newAppPublicPath } = require("./newApp") +const download = require("download") +const env = require("../../environment") +const { homedir } = require("os") +const fetch = require("node-fetch") + +const DEFAULT_AUTOMATION_BUCKET = + "https://prod-budi-automations.s3-eu-west-1.amazonaws.com" +const DEFAULT_AUTOMATION_DIRECTORY = ".budibase-automations" + +/** + * The single stack system (Cloud and Builder) should not make use of the file system where possible, + * this file handles all of the file access for the system with the intention of limiting it all to one + * place. Keeping all of this logic in one place means that when we need to do file system access (like + * downloading a package or opening a temporary file) in can be done in way that we can confirm it shouldn't + * be done through an object store instead. + */ + +/** + * Upon first startup of instance there may not be everything we need in tmp directory, set it up. + */ +exports.init = () => { + const tempDir = budibaseTempDir() + if (!fs.existsSync(tempDir)) { + fs.mkdirSync(tempDir) + } +} + +/** + * Checks if the system is currently in development mode and if it is makes sure + * everything required to function is ready. + */ +exports.checkDevelopmentEnvironment = () => { + if (!isDev()) { + return + } + let error + if (!fs.existsSync(budibaseTempDir())) { + error = + "Please run a build before attempting to run server independently to fill 'tmp' directory." + } + if (!fs.existsSync(join(process.cwd(), ".env"))) { + error = "Must run via yarn once to generate environment." + } + if (error) { + console.error(error) + process.exit(-1) + } +} + +/** + * This function manages temporary template files which are stored by Koa. + * @param {Object} template The template object retrieved from the Koa context object. + * @returns {Object} Returns an fs read stream which can be loaded into the database. + */ +exports.getTemplateStream = async template => { + if (template.file) { + return fs.createReadStream(template.file.path) + } else { + const tmpPath = await exports.downloadTemplate(...template.key.split("/")) + return fs.createReadStream(join(tmpPath, "db", "dump.txt")) + } +} + +/** + * Used to retrieve a handlebars file from the system which will be used as a template. + * This is allowable as the template handlebars files should be static and identical across + * the cluster. + * @param {string} path The path to the handlebars file which is to be loaded. + * @returns {string} The loaded handlebars file as a string - loaded as utf8. + */ +exports.loadHandlebarsFile = path => { + return fs.readFileSync(path, "utf8") +} + +/** + * When return a file from the API need to write the file to the system temporarily so we + * can create a read stream to send. + * @param {string} contents the contents of the file which is to be returned from the API. + * @return {Object} the read stream which can be put into the koa context body. + */ +exports.apiFileReturn = contents => { + const path = join(budibaseTempDir(), uuid()) + fs.writeFileSync(path, contents) + return fs.createReadStream(path) +} + +/** + * Takes a copy of the database state for an app to the object store. + * @param {string} appId The ID of the app which is to be backed up. + * @param {string} backupName The name of the backup located in the object store. + * @return The backup has been completed when this promise completes and returns a file stream + * to the temporary backup file (to return via API if required). + */ +exports.performBackup = async (appId, backupName) => { + const path = join(budibaseTempDir(), backupName) + const writeStream = fs.createWriteStream(path) + // perform couch dump + const instanceDb = new CouchDB(appId) + await instanceDb.dump(writeStream, {}) + // write the file to the object store + await streamUpload( + ObjectStoreBuckets.BACKUPS, + join(appId, backupName), + fs.createReadStream(path) + ) + return fs.createReadStream(path) +} + +/** + * Downloads required libraries and creates a new path in the object store. + * @param {string} appId The ID of the app which is being created. + * @return {Promise} once promise completes app resources should be ready in object store. + */ +exports.createApp = async appId => { + await downloadLibraries(appId) + await newAppPublicPath(appId) +} + +/** + * Removes all of the assets created for an app in the object store. + * @param {string} appId The ID of the app which is being deleted. + * @return {Promise} once promise completes the app resources will be removed from object store. + */ +exports.deleteApp = async appId => { + await deleteFolder(ObjectStoreBuckets.APPS, `${appId}/`) +} + +/** + * Retrieves a template and pipes it to minio as well as making it available temporarily. + * @param {string} type The type of template which is to be retrieved. + * @param name + * @return {Promise<*>} + */ +exports.downloadTemplate = async (type, name) => { + const DEFAULT_TEMPLATES_BUCKET = + "prod-budi-templates.s3-eu-west-1.amazonaws.com" + const templateUrl = `https://${DEFAULT_TEMPLATES_BUCKET}/templates/${type}/${name}.tar.gz` + return downloadTarball(templateUrl, ObjectStoreBuckets.TEMPLATES, type) +} + +/** + * Retrieves component libraries from object store (or tmp symlink if in local) + */ +exports.getComponentLibraryManifest = async (appId, library) => { + const path = join(appId, "node_modules", library, "package", "manifest.json") + let resp = await retrieve(ObjectStoreBuckets.APPS, path) + if (typeof resp !== "string") { + resp = resp.toString("utf8") + } + return JSON.parse(resp) +} + +exports.automationInit = async () => { + const directory = + env.AUTOMATION_DIRECTORY || join(homedir(), DEFAULT_AUTOMATION_DIRECTORY) + const bucket = env.AUTOMATION_BUCKET || DEFAULT_AUTOMATION_BUCKET + if (!fs.existsSync(directory)) { + fs.mkdirSync(directory, { recursive: true }) + } + // env setup to get async packages + let response = await fetch(`${bucket}/manifest.json`) + return response.json() +} + +exports.getExternalAutomationStep = async (name, version, bundleName) => { + const directory = + env.AUTOMATION_DIRECTORY || join(homedir(), DEFAULT_AUTOMATION_DIRECTORY) + const bucket = env.AUTOMATION_BUCKET || DEFAULT_AUTOMATION_BUCKET + try { + return require(join(directory, bundleName)) + } catch (err) { + await download(`${bucket}/${name}/${version}/${bundleName}`, directory) + return require(join(directory, bundleName)) + } +} + +/** + * All file reads come through here just to make sure all of them make sense + * allows a centralised location to check logic is all good. + */ +exports.readFileSync = (filepath, options = "utf8") => { + return fs.readFileSync(filepath, options) +} + +/** + * Full function definition for below can be found in the utilities. + */ +exports.upload = upload +exports.retrieve = retrieve diff --git a/packages/server/src/utilities/fileSystem/newApp.js b/packages/server/src/utilities/fileSystem/newApp.js new file mode 100644 index 0000000000..ba3d13afed --- /dev/null +++ b/packages/server/src/utilities/fileSystem/newApp.js @@ -0,0 +1,34 @@ +const packageJson = require("../../../package.json") +const { join } = require("path") +const { ObjectStoreBuckets } = require("../../constants") +const { streamUpload, downloadTarball } = require("./utilities") +const fs = require("fs") + +const BUCKET_NAME = ObjectStoreBuckets.APPS + +// can't really test this due to the downloading nature of it, wouldn't be a great test case +/* istanbul ignore next */ +exports.downloadLibraries = async appId => { + const LIBRARIES = ["standard-components"] + + // Need to download tarballs directly from NPM as our users may not have node on their machine + for (let lib of LIBRARIES) { + // download tarball + const registryUrl = `https://registry.npmjs.org/@budibase/${lib}/-/${lib}-${packageJson.version}.tgz` + const path = join(appId, "node_modules", "@budibase", lib) + await downloadTarball(registryUrl, BUCKET_NAME, path) + } +} + +exports.newAppPublicPath = async appId => { + const path = join(appId, "public") + const sourcepath = require.resolve("@budibase/client") + const destPath = join(path, "budibase-client.js") + + await streamUpload(BUCKET_NAME, destPath, fs.createReadStream(sourcepath)) + await streamUpload( + BUCKET_NAME, + destPath + ".map", + fs.createReadStream(sourcepath + ".map") + ) +} diff --git a/packages/server/src/utilities/fileProcessor.js b/packages/server/src/utilities/fileSystem/processor.js similarity index 62% rename from packages/server/src/utilities/fileProcessor.js rename to packages/server/src/utilities/fileSystem/processor.js index 15132b2d49..3778b50168 100644 --- a/packages/server/src/utilities/fileProcessor.js +++ b/packages/server/src/utilities/fileSystem/processor.js @@ -1,25 +1,20 @@ -const fs = require("fs") const jimp = require("jimp") -const fsPromises = fs.promises const FORMATS = { IMAGES: ["png", "jpg", "jpeg", "gif", "bmp", "tiff"], } function processImage(file) { + // this will overwrite the temp file return jimp.read(file.path).then(img => { - return img.resize(300, jimp.AUTO).write(file.outputPath) + return img.resize(300, jimp.AUTO).write(file.path) }) } async function process(file) { if (FORMATS.IMAGES.includes(file.extension.toLowerCase())) { await processImage(file) - return file } - - // No processing required - await fsPromises.copyFile(file.path, file.outputPath) return file } diff --git a/packages/server/src/utilities/fileSystem/utilities.js b/packages/server/src/utilities/fileSystem/utilities.js new file mode 100644 index 0000000000..db515106c0 --- /dev/null +++ b/packages/server/src/utilities/fileSystem/utilities.js @@ -0,0 +1,221 @@ +const sanitize = require("sanitize-s3-objectkey") +const AWS = require("aws-sdk") +const stream = require("stream") +const fetch = require("node-fetch") +const tar = require("tar-fs") +const zlib = require("zlib") +const { promisify } = require("util") +const { join } = require("path") +const fs = require("fs") +const { budibaseTempDir } = require("../budibaseDir") +const env = require("../../environment") +const { ObjectStoreBuckets } = require("../../constants") + +const streamPipeline = promisify(stream.pipeline) + +const CONTENT_TYPE_MAP = { + html: "text/html", + css: "text/css", + js: "application/javascript", +} +const STRING_CONTENT_TYPES = [ + CONTENT_TYPE_MAP.html, + CONTENT_TYPE_MAP.css, + CONTENT_TYPE_MAP.js, +] + +function publicPolicy(bucketName) { + return { + Version: "2012-10-17", + Statement: [ + { + Effect: "Allow", + Principal: { + AWS: ["*"], + }, + Action: "s3:GetObject", + Resource: [`arn:aws:s3:::${bucketName}/*`], + }, + ], + } +} + +const PUBLIC_BUCKETS = [ObjectStoreBuckets.APPS] + +/** + * Gets a connection to the object store using the S3 SDK. + * @param {string} bucket the name of the bucket which blobs will be uploaded/retrieved from. + * @return {Object} an S3 object store object, check S3 Nodejs SDK for usage. + * @constructor + */ +exports.ObjectStore = bucket => { + if (env.SELF_HOSTED) { + AWS.config.update({ + accessKeyId: env.MINIO_ACCESS_KEY, + secretAccessKey: env.MINIO_SECRET_KEY, + }) + } + const config = { + s3ForcePathStyle: true, + signatureVersion: "v4", + params: { + Bucket: bucket, + }, + } + if (env.MINIO_URL) { + config.endpoint = env.MINIO_URL + } + return new AWS.S3(config) +} + +/** + * Given an object store and a bucket name this will make sure the bucket exists, + * if it does not exist then it will create it. + */ +exports.makeSureBucketExists = async (client, bucketName) => { + try { + await client + .headBucket({ + Bucket: bucketName, + }) + .promise() + } catch (err) { + // bucket doesn't exist create it + if (err.statusCode === 404) { + await client + .createBucket({ + Bucket: bucketName, + }) + .promise() + // public buckets are quite hidden in the system, make sure + // no bucket is set accidentally + if (PUBLIC_BUCKETS.includes(bucketName)) { + await client + .putBucketPolicy({ + Bucket: bucketName, + Policy: JSON.stringify(publicPolicy(bucketName)), + }) + .promise() + } + } else { + throw err + } + } +} + +/** + * Uploads the contents of a file given the required parameters, useful when + * temp files in use (for example file uploaded as an attachment). + */ +exports.upload = async ({ bucket, filename, path, type, metadata }) => { + const extension = [...filename.split(".")].pop() + const fileBytes = fs.readFileSync(path) + + const objectStore = exports.ObjectStore(bucket) + await exports.makeSureBucketExists(objectStore, bucket) + + const config = { + // windows file paths need to be converted to forward slashes for s3 + Key: sanitize(filename).replace(/\\/g, "/"), + Body: fileBytes, + ContentType: type || CONTENT_TYPE_MAP[extension.toLowerCase()], + } + if (metadata) { + config.Metadata = metadata + } + return objectStore.upload(config).promise() +} + +/** + * Similar to the upload function but can be used to send a file stream + * through to the object store. + */ +exports.streamUpload = async (bucket, filename, stream) => { + const objectStore = exports.ObjectStore(bucket) + await exports.makeSureBucketExists(objectStore, bucket) + + const params = { + Bucket: bucket, + Key: sanitize(filename).replace(/\\/g, "/"), + Body: stream, + } + return objectStore.upload(params).promise() +} + +/** + * retrieves the contents of a file from the object store, if it is a known content type it + * will be converted, otherwise it will be returned as a buffer stream. + */ +exports.retrieve = async (bucket, filename) => { + const objectStore = exports.ObjectStore(bucket) + const params = { + Bucket: bucket, + Key: sanitize(filename).replace(/\\/g, "/"), + } + const response = await objectStore.getObject(params).promise() + // currently these are all strings + if (STRING_CONTENT_TYPES.includes(response.ContentType)) { + return response.Body.toString("utf8") + } else { + return response.Body + } +} + +exports.deleteFolder = async (bucket, folder) => { + const client = exports.ObjectStore(bucket) + const listParams = { + Bucket: bucket, + Prefix: folder, + } + + let response = await client.listObjects(listParams).promise() + if (response.Contents.length === 0) { + return + } + const deleteParams = { + Bucket: bucket, + Delete: { + Objects: [], + }, + } + + response.Contents.forEach(content => { + deleteParams.Delete.Objects.push({ Key: content.Key }) + }) + + response = await client.deleteObjects(deleteParams).promise() + // can only empty 1000 items at once + if (response.Deleted.length === 1000) { + return exports.deleteFolder(bucket, folder) + } +} + +exports.uploadDirectory = async (bucket, localPath, bucketPath) => { + let uploads = [] + const files = fs.readdirSync(localPath, { withFileTypes: true }) + for (let file of files) { + const path = join(bucketPath, file.name) + const local = join(localPath, file.name) + if (file.isDirectory()) { + uploads.push(exports.uploadDirectory(bucket, local, path)) + } else { + uploads.push( + exports.streamUpload(bucket, path, fs.createReadStream(local)) + ) + } + } + await Promise.all(uploads) +} + +exports.downloadTarball = async (url, bucket, path) => { + const response = await fetch(url) + if (!response.ok) { + throw new Error(`unexpected response ${response.statusText}`) + } + + const tmpPath = join(budibaseTempDir(), path) + await streamPipeline(response.body, zlib.Unzip(), tar.extract(tmpPath)) + await exports.uploadDirectory(bucket, tmpPath, path) + // return the temporary path incase there is a use for it + return tmpPath +} diff --git a/packages/server/src/utilities/index.js b/packages/server/src/utilities/index.js index 7420884d44..7d6794b1b3 100644 --- a/packages/server/src/utilities/index.js +++ b/packages/server/src/utilities/index.js @@ -1,6 +1,5 @@ const env = require("../environment") const { DocumentTypes, SEPARATOR } = require("../db/utils") -const fs = require("fs") const CouchDB = require("../db") const APP_PREFIX = DocumentTypes.APP + SEPARATOR @@ -13,14 +12,7 @@ function confirmAppId(possibleAppId) { exports.wait = ms => new Promise(resolve => setTimeout(resolve, ms)) -exports.isDev = () => { - return ( - !env.CLOUD && - env.NODE_ENV !== "production" && - env.NODE_ENV !== "jest" && - env.NODE_ENV !== "cypress" - ) -} +exports.isDev = env.isDev /** * Given a request tries to find the appId, which can be located in various places @@ -28,10 +20,18 @@ exports.isDev = () => { * @returns {string|undefined} If an appId was found it will be returned. */ exports.getAppId = ctx => { - let appId = confirmAppId(ctx.headers["x-budibase-app-id"]) - if (!appId) { - appId = confirmAppId(env.CLOUD ? ctx.subdomains[1] : ctx.params.appId) + const options = [ctx.headers["x-budibase-app-id"], ctx.params.appId] + if (ctx.subdomains) { + options.push(ctx.subdomains[1]) } + let appId + for (let option of options) { + appId = confirmAppId(option) + if (appId) { + break + } + } + // look in body if can't find it in subdomain if (!appId && ctx.request.body && ctx.request.body.appId) { appId = confirmAppId(ctx.request.body.appId) @@ -51,7 +51,7 @@ exports.getAppId = ctx => { * @returns {string} The name of the token trying to find */ exports.getCookieName = (name = "builder") => { - let environment = env.CLOUD ? "cloud" : "local" + let environment = env.isProd() ? "cloud" : "local" return `budibase:${name}:${environment}` } @@ -89,24 +89,6 @@ exports.isClient = ctx => { return ctx.headers["x-budibase-type"] === "client" } -/** - * Recursively walk a directory tree and execute a callback on all files. - * @param {String} dirPath - Directory to traverse - * @param {Function} callback - callback to execute on files - */ -exports.walkDir = (dirPath, callback) => { - for (let filename of fs.readdirSync(dirPath)) { - const filePath = `${dirPath}/${filename}` - const stat = fs.lstatSync(filePath) - - if (stat.isFile()) { - callback(filePath) - } else { - exports.walkDir(filePath, callback) - } - } -} - exports.getLogoUrl = () => { return "https://d33wubrfki0l68.cloudfront.net/aac32159d7207b5085e74a7ef67afbb7027786c5/2b1fd/img/logo/bb-emblem.svg" } diff --git a/packages/server/src/utilities/initialiseBudibase.js b/packages/server/src/utilities/initialiseBudibase.js deleted file mode 100644 index bdbd194eaa..0000000000 --- a/packages/server/src/utilities/initialiseBudibase.js +++ /dev/null @@ -1,37 +0,0 @@ -const { existsSync, readFile, writeFile, ensureDir } = require("fs-extra") -const { join, resolve } = require("./centralPath") -const { processString } = require("@budibase/string-templates") -const uuid = require("uuid") - -module.exports = async opts => { - await ensureDir(opts.dir) - await setCouchDbUrl(opts) - - // need an env file - await createDevEnvFile(opts) -} - -const setCouchDbUrl = async opts => { - if (!opts.couchDbUrl) { - const dataDir = join(opts.dir, ".data") - await ensureDir(dataDir) - opts.couchDbUrl = - dataDir + (dataDir.endsWith("/") || dataDir.endsWith("\\") ? "" : "/") - } -} - -const createDevEnvFile = async opts => { - const destConfigFile = join(opts.dir, "./.env") - let createConfig = !existsSync(destConfigFile) || opts.quiet - if (createConfig) { - const template = await readFile( - resolve(__dirname, "..", "..", ".env.template"), - { - encoding: "utf8", - } - ) - opts.cookieKey1 = opts.cookieKey1 || uuid.v4() - const config = await processString(template, opts) - await writeFile(destConfigFile, config, { flag: "w+" }) - } -} diff --git a/packages/server/src/utilities/rowProcessor.js b/packages/server/src/utilities/rowProcessor.js index eddd597459..97e2a2880c 100644 --- a/packages/server/src/utilities/rowProcessor.js +++ b/packages/server/src/utilities/rowProcessor.js @@ -180,7 +180,7 @@ exports.outputProcessing = async (appId, table, rows) => { rows ) // update the attachments URL depending on hosting - if (env.CLOUD && env.SELF_HOSTED) { + if (env.isProd() && env.SELF_HOSTED) { for (let [property, column] of Object.entries(table.schema)) { if (column.type === FieldTypes.ATTACHMENT) { for (let row of outputRows) { diff --git a/packages/server/src/utilities/templates.js b/packages/server/src/utilities/templates.js deleted file mode 100644 index c3d89477df..0000000000 --- a/packages/server/src/utilities/templates.js +++ /dev/null @@ -1,81 +0,0 @@ -const fs = require("fs-extra") -const { join } = require("./centralPath") -const os = require("os") -const fetch = require("node-fetch") -const stream = require("stream") -const tar = require("tar-fs") -const zlib = require("zlib") -const { promisify } = require("util") -const streamPipeline = promisify(stream.pipeline) -const { budibaseAppsDir } = require("./budibaseDir") -const env = require("../environment") -const CouchDB = require("../db") - -const DEFAULT_TEMPLATES_BUCKET = - "prod-budi-templates.s3-eu-west-1.amazonaws.com" - -exports.getLocalTemplates = function() { - const templatesDir = join(os.homedir(), ".budibase", "templates", "app") - const templateObj = { app: {} } - fs.ensureDirSync(templatesDir) - const templateNames = fs.readdirSync(templatesDir) - for (let name of templateNames) { - templateObj.app[name] = { - name, - category: "local", - description: "local template", - type: "app", - key: `app/${name}`, - } - } - return templateObj -} - -// can't really test this, downloading is just not something we should do in a behavioural test -/* istanbul ignore next */ -exports.downloadTemplate = async function(type, name) { - const dirName = join(budibaseAppsDir(), "templates", type, name) - if (env.LOCAL_TEMPLATES) { - return dirName - } - const templateUrl = `https://${DEFAULT_TEMPLATES_BUCKET}/templates/${type}/${name}.tar.gz` - const response = await fetch(templateUrl) - - if (!response.ok) { - throw new Error( - `Error downloading template ${type}:${name}: ${response.statusText}` - ) - } - - // stream the response, unzip and extract - await streamPipeline( - response.body, - zlib.Unzip(), - tar.extract(join(budibaseAppsDir(), "templates", type)) - ) - - return dirName -} - -async function performDump({ dir, appId, name = "dump.txt" }) { - const writeStream = fs.createWriteStream(join(dir, name)) - // perform couch dump - const instanceDb = new CouchDB(appId) - await instanceDb.dump(writeStream, {}) -} - -exports.performDump = performDump - -exports.exportTemplateFromApp = async function({ templateName, appId }) { - // Copy frontend files - const templatesDir = join( - budibaseAppsDir(), - "templates", - "app", - templateName, - "db" - ) - fs.ensureDirSync(templatesDir) - await performDump({ dir: templatesDir, appId }) - return templatesDir -} diff --git a/packages/server/src/utilities/tests/csvParser.spec.js b/packages/server/src/utilities/tests/csvParser.spec.js index 48e275fdd1..76ea9a7eb3 100644 --- a/packages/server/src/utilities/tests/csvParser.spec.js +++ b/packages/server/src/utilities/tests/csvParser.spec.js @@ -1,4 +1,4 @@ -const fs = require("fs") +const { readFileSync } = require("../fileSystem") const csvParser = require("../csvParser") const CSV_PATH = __dirname + "/test.csv" @@ -33,7 +33,7 @@ const SCHEMAS = { } describe("CSV Parser", () => { - const csvString = fs.readFileSync(CSV_PATH, "utf8") + const csvString = readFileSync(CSV_PATH, "utf8") describe("parsing", () => { it("returns status and types for a valid CSV transformation", async () => { diff --git a/packages/server/src/utilities/usageQuota.js b/packages/server/src/utilities/usageQuota.js index d809d9e673..d042d290d5 100644 --- a/packages/server/src/utilities/usageQuota.js +++ b/packages/server/src/utilities/usageQuota.js @@ -50,7 +50,7 @@ exports.Properties = { } exports.getAPIKey = async appId => { - if (env.SELF_HOSTED) { + if (!env.USE_QUOTAS) { return { apiKey: null } } return apiKeyTable.get({ primary: appId }) @@ -65,8 +65,7 @@ exports.getAPIKey = async appId => { * also been reset after this call. */ exports.update = async (apiKey, property, usage) => { - // don't try validate in builder - if (!env.CLOUD || env.SELF_HOSTED) { + if (!env.USE_QUOTAS) { return } try { diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index 8756768f96..29c46c691c 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -214,6 +214,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" +"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d" + integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@^7.7.2": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.1.tgz#b4116a6b6711d010b2dad3b7b6e43bf1b9954740" @@ -267,11 +274,11 @@ turndown "^7.0.0" "@budibase/client@^0.8.9": - version "0.8.12" - resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.8.12.tgz#1186d95509e28bb660f0c801b37b3cc52b0f3864" - integrity sha512-68eh1QvF443OgMsjpDEgSY9D/MUutnaKFruP98PHJgrRkVhQDY7F2EKn2FWgC5NJuCY3aJ1emRPdXUq4MapWuA== + version "0.8.10" + resolved "https://registry.yarnpkg.com/@budibase/client/-/client-0.8.10.tgz#18cd929dddfd2fa65c15e23871a26bccaa03049f" + integrity sha512-C/25rBAspQDk7Lp8w9icEcytgMAm8+7l+A4Lp7egpDJvrH+5jq2kVNuRRi3emAddCSTBmM0amIoS+PHUGvAa9Q== dependencies: - "@budibase/string-templates" "^0.8.12" + "@budibase/string-templates" "^0.8.10" regexparam "^1.3.0" shortid "^2.2.15" svelte-spa-router "^3.0.5" @@ -309,9 +316,9 @@ year "^0.2.1" "@budibase/standard-components@^0.8.9": - version "0.8.12" - resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.8.12.tgz#dfb4b155c927a169c4035ad837e739d80c1386d9" - integrity sha512-+0h/I1MYxHjGuTOQbrGOG+P3ayIQ6gIGJ+5qjewctb9FjAIr3/aui6EWoyxfjjC/cVGVPdu8WnUdE6SWDrYhFw== + version "0.8.10" + resolved "https://registry.yarnpkg.com/@budibase/standard-components/-/standard-components-0.8.10.tgz#ffde4e3bfe5afe14c242d7066328066500333b62" + integrity sha512-zXE9SbMFxrZ8tpYmTaBQEPwT+3xJ8p4QtDkSRfnEcxbWojNghh5ikn0897FpgnP7q9x+63tDQ0UCiUcNoiOk7w== dependencies: "@adobe/spectrum-css-workflow-icons" "^1.1.0" "@budibase/bbui" "^1.58.13" @@ -340,10 +347,10 @@ svelte-flatpickr "^3.1.0" turndown "^7.0.0" -"@budibase/string-templates@^0.8.12", "@budibase/string-templates@^0.8.9": - version "0.8.12" - resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.8.12.tgz#bc08d441ac90b2f71c89b4b15c40c7c5e934a6fb" - integrity sha512-FkTm3XRBozhrJqJA4YdDxbLbcY3me7sAbJ+dgO7mZkTybRcrdbxRwL1XjYfRw9DL7+8pucN2Y6OW8gOQgyaTtw== +"@budibase/string-templates@^0.8.10", "@budibase/string-templates@^0.8.9": + version "0.8.10" + resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-0.8.10.tgz#57b1cfb44d4bdac0ab3ef9a4912a6185e972711b" + integrity sha512-YUtbbAvLcxpeEiCs36cF5bkEDgpLaklvpuG/pcUIOxAPr1feqcN1KyFbuoSPihZOWXjXLgsujQerQ5xn8MUrhg== dependencies: "@budibase/handlebars-helpers" "^0.11.3" dayjs "^1.10.4" @@ -1109,6 +1116,28 @@ dependencies: "@babel/types" "^7.3.0" +"@types/body-parser@*": + version "1.19.0" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" + integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/bull@^3.14.4": + version "3.15.0" + resolved "https://registry.yarnpkg.com/@types/bull/-/bull-3.15.0.tgz#69c518d4e7a53056f287cebcc4ef4ffe91aaf201" + integrity sha512-54Y1RYkJt6i+4dH45w4gZOP6fyhksTvOImfgBYAxgq/nt5ZrES4xFWwOzt2bxAgSR7FMH9fwvaiJN/pripPzag== + dependencies: + "@types/ioredis" "*" + +"@types/connect@*": + version "3.4.34" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.34.tgz#170a40223a6d666006d93ca128af2beb1d9b1901" + integrity sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ== + dependencies: + "@types/node" "*" + "@types/debug@^4.1.5": version "4.1.5" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" @@ -1119,6 +1148,25 @@ resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== +"@types/express-serve-static-core@^4.17.14", "@types/express-serve-static-core@^4.17.18": + version "4.17.19" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz#00acfc1632e729acac4f1530e9e16f6dd1508a1d" + integrity sha512-DJOSHzX7pCiSElWaGR8kCprwibCB/3yW6vcT8VG3P0SJjnv19gnWG/AZMfM60Xj/YJIp/YCaDHyvzsFVeniARA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@^4.17.9": + version "4.17.11" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.11.tgz#debe3caa6f8e5fcda96b47bd54e2f40c4ee59545" + integrity sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/formidable@^1.0.31": version "1.0.31" resolved "https://registry.yarnpkg.com/@types/formidable/-/formidable-1.0.31.tgz#274f9dc2d0a1a9ce1feef48c24ca0859e7ec947b" @@ -1134,6 +1182,13 @@ dependencies: "@types/node" "*" +"@types/ioredis@*": + version "4.22.1" + resolved "https://registry.yarnpkg.com/@types/ioredis/-/ioredis-4.22.1.tgz#e03b064189ab20c9edc95ccde6228d2bf81f538c" + integrity sha512-GxXT828fkvBeThO68ZJg8cD2haqea5ANBJaxA+UZqLranNkEnQ8N7QLPtykwWbN/sRQz75O7kj+PNmCKF4CEKw== + dependencies: + "@types/node" "*" + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" @@ -1154,6 +1209,11 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + "@types/node@*": version "14.11.8" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.8.tgz#fe2012f2355e4ce08bca44aeb3abbb21cf88d33f" @@ -1189,6 +1249,37 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== +"@types/prop-types@*": + version "15.7.3" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" + integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== + +"@types/qs@*": + version "6.9.6" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.6.tgz#df9c3c8b31a247ec315e6996566be3171df4b3b1" + integrity sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA== + +"@types/range-parser@*": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" + integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== + +"@types/react-dom@^17.0.2": + version "17.0.3" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.3.tgz#7fdf37b8af9d6d40127137865bb3fff8871d7ee1" + integrity sha512-4NnJbCeWE+8YBzupn/YrJxZ8VnjcJq5iR1laqQ1vkpQgBiA7bwk0Rp24fxsdNinzJY2U+HHS4dJJDPdoMjdJ7w== + dependencies: + "@types/react" "*" + +"@types/react@*": + version "17.0.3" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.3.tgz#ba6e215368501ac3826951eef2904574c262cc79" + integrity sha512-wYOUxIgs2HZZ0ACNiIayItyluADNbONl7kt8lkLjVK8IitMH5QMyAh75Fwhmo37r1m7L2JaFj03sIfxBVDvRAg== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/readable-stream@^2.3.5": version "2.3.9" resolved "https://registry.yarnpkg.com/@types/readable-stream/-/readable-stream-2.3.9.tgz#40a8349e6ace3afd2dd1b6d8e9b02945de4566a9" @@ -1197,11 +1288,24 @@ "@types/node" "*" safe-buffer "*" +"@types/scheduler@*": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275" + integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA== + "@types/semver@^7.1.0": version "7.3.4" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.4.tgz#43d7168fec6fa0988bb1a513a697b29296721afb" integrity sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ== +"@types/serve-static@*": + version "1.13.9" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.9.tgz#aacf28a85a05ee29a11fb7c3ead935ac56f33e4e" + integrity sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" @@ -1292,7 +1396,7 @@ abstract-leveldown@~6.2.1, abstract-leveldown@~6.2.3: level-supports "~1.0.0" xtend "~4.0.0" -accepts@^1.3.5: +accepts@^1.3.5, accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== @@ -1805,6 +1909,11 @@ array-equal@^1.0.0: resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + array-sort@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/array-sort/-/array-sort-0.1.4.tgz#662855eaeb671b4188df4451b2f24a0753992b23" @@ -2086,6 +2195,22 @@ bmp-js@^0.1.0: resolved "https://registry.yarnpkg.com/bmp-js/-/bmp-js-0.1.0.tgz#e05a63f796a6c1ff25f4771ec7adadc148c07233" integrity sha1-4Fpj95amwf8l9Hcex62twUjAcjM= +body-parser@1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" + integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== + dependencies: + bytes "3.1.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.7.2" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.7.0" + raw-body "2.4.0" + type-is "~1.6.17" + boolean@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.1.tgz#35ecf2b4a2ee191b0b44986f14eb5f052a5cbb4f" @@ -2272,6 +2397,44 @@ builder-util@22.9.1: stat-mode "^1.0.0" temp-file "^3.3.7" +bull-board@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/bull-board/-/bull-board-1.5.1.tgz#748b0feb2fc90b9d05b2d897c298e71e6f353158" + integrity sha512-oHcAQoZOruWDB2ir36KFpvTpOt8lUCu2Oo8ZOPWW8nL14QXlZlxhzbyAhcw1g10QifnGP3unRkL43hK5cXhuKw== + dependencies: + "@types/bull" "^3.14.4" + "@types/express" "^4.17.9" + "@types/express-serve-static-core" "^4.17.14" + "@types/react-dom" "^17.0.2" + axios "^0.21.1" + classnames "^2.2.6" + date-fns "2.19.0" + ejs "3.1.6" + express "4.17.1" + highlight.js "^10.7.1" + pretty-bytes "5.6.0" + react "17.0.1" + react-dom "17.0.1" + react-router-dom "^5.2.0" + react-toastify "^7.0.3" + redis-info "^3.0.8" + +bull@^3.22.0: + version "3.22.0" + resolved "https://registry.yarnpkg.com/bull/-/bull-3.22.0.tgz#fb04b68189bd49e56155f4366df96330c059868c" + integrity sha512-csQTIuvoKnVuW6gbZmIe9mVkLy2DzvRodywjXN7cfYlvXKme3156FIc1Zssn5IRKpDKyyq0++AYsLO4mdtnf0Q== + dependencies: + cron-parser "^2.13.0" + debuglog "^1.0.0" + get-port "^5.1.1" + ioredis "^4.22.0" + lodash "^4.17.21" + p-timeout "^3.2.0" + promise.prototype.finally "^3.1.2" + semver "^7.3.2" + util.promisify "^1.0.1" + uuid "^8.3.0" + bytes@3.1.0, bytes@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" @@ -2441,6 +2604,11 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" +classnames@^2.2.6: + version "2.2.6" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" + integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== + clean-stack@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" @@ -2498,6 +2666,16 @@ clone@^2.1.1: resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= +clsx@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" + integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== + +cluster-key-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d" + integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw== + co-body@^5.1.1: version "5.2.0" resolved "https://registry.yarnpkg.com/co-body/-/co-body-5.2.0.tgz#5a0a658c46029131e0e3a306f67647302f71c124" @@ -2636,14 +2814,14 @@ console-clear@^1.1.0: resolved "https://registry.yarnpkg.com/console-clear/-/console-clear-1.1.1.tgz#995e20cbfbf14dd792b672cde387bd128d674bf7" integrity sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ== -content-disposition@^0.5.2, content-disposition@~0.5.2: +content-disposition@0.5.3, content-disposition@^0.5.2, content-disposition@~0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== dependencies: safe-buffer "5.1.2" -content-type@^1.0.4: +content-type@^1.0.4, content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== @@ -2655,6 +2833,16 @@ convert-source-map@^1.4.0, convert-source-map@^1.7.0: dependencies: safe-buffer "~5.1.1" +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + +cookie@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" + integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== + cookie@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" @@ -2695,12 +2883,13 @@ crc@^3.4.4: dependencies: buffer "^5.1.0" -cross-env@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" - integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== +cron-parser@^2.13.0: + version "2.18.0" + resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.18.0.tgz#de1bb0ad528c815548371993f81a54e5a089edcf" + integrity sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg== dependencies: - cross-spawn "^7.0.1" + is-nan "^1.3.0" + moment-timezone "^0.5.31" cross-spawn@^4.0.0: version "4.0.2" @@ -2721,15 +2910,6 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -2747,6 +2927,11 @@ cssstyle@^1.0.0: dependencies: cssom "0.3.x" +csstype@^3.0.2: + version "3.0.7" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.7.tgz#2a5fb75e1015e84dd15692f71e89a1450290950b" + integrity sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g== + csvtojson@2.0.10: version "2.0.10" resolved "https://registry.yarnpkg.com/csvtojson/-/csvtojson-2.0.10.tgz#11e7242cc630da54efce7958a45f443210357574" @@ -2772,6 +2957,11 @@ data-urls@^1.0.0: whatwg-mimetype "^2.2.0" whatwg-url "^7.0.0" +date-fns@2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.19.0.tgz#65193348635a28d5d916c43ec7ce6fbd145059e1" + integrity sha512-X3bf2iTPgCAQp9wvjOQytnf5vO5rESYRXlPIVcgSbtT5OTScPcsf9eZU+B/YIkKAtYr5WeCii58BgATrNitlWg== + date-utils@*: version "1.2.21" resolved "https://registry.yarnpkg.com/date-utils/-/date-utils-1.2.21.tgz#61fb16cdc1274b3c9acaaffe9fc69df8720a2b64" @@ -2794,6 +2984,13 @@ dayjs@^1.10.4: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.4.tgz#8e544a9b8683f61783f570980a8a80eaf54ab1e2" integrity sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw== +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + debug@4, debug@^4.0.1: version "4.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" @@ -2808,13 +3005,6 @@ debug@=3.1.0, debug@~3.1.0: dependencies: ms "2.0.0" -debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - debug@^3.1.0, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -2822,13 +3012,18 @@ debug@^3.1.0, debug@^3.2.6: dependencies: ms "^2.1.1" -debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.0: +debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.0, debug@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== dependencies: ms "2.1.2" +debuglog@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" + integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -3008,6 +3203,11 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= +denque@^1.1.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.0.tgz#773de0686ff2d8ec2ff92914316a47b73b1c73de" + integrity sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ== + denque@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf" @@ -3023,7 +3223,7 @@ depd@^2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -destroy@^1.0.4: +destroy@^1.0.4, destroy@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= @@ -3063,6 +3263,11 @@ dmg-builder@22.9.1: js-yaml "^3.14.0" sanitize-filename "^1.6.3" +docker-compose@^0.23.6: + version "0.23.6" + resolved "https://registry.yarnpkg.com/docker-compose/-/docker-compose-0.23.6.tgz#bd21e17d599f17fcf7a4b5d607cff0358a9c378b" + integrity sha512-y3Q8MkwG862rNqkvEQG59/7Fi2/fzs3NYDCvqUAAD+z0WGs2qcJ9hRcn34hWgWv9ouPkFqe3Vwca0h+4bIIRWw== + doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" @@ -3151,6 +3356,13 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= +ejs@3.1.6: + version "3.1.6" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a" + integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw== + dependencies: + jake "^10.6.1" + ejs@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.5.tgz#aed723844dc20acb4b170cd9ab1017e476a0d93b" @@ -3269,7 +3481,7 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -encodeurl@^1.0.2: +encodeurl@^1.0.2, encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= @@ -3342,6 +3554,28 @@ error-symbol@^0.1.0: resolved "https://registry.yarnpkg.com/error-symbol/-/error-symbol-0.1.0.tgz#0a4dae37d600d15a29ba453d8ef920f1844333f6" integrity sha1-Ck2uN9YA0VopukU9jvkg8YRDM/Y= +es-abstract@^1.17.0-next.0: + version "1.18.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4" + integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.2" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.2" + is-string "^1.0.5" + object-inspect "^1.9.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.0" + es-abstract@^1.18.0-next.2: version "1.18.0-next.3" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.3.tgz#56bc8b5cc36b2cca25a13be07f3c02c2343db6b7" @@ -3411,7 +3645,7 @@ escape-goat@^2.0.0: resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== -escape-html@^1.0.3: +escape-html@^1.0.3, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= @@ -3579,6 +3813,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + event-target-shim@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" @@ -3660,6 +3899,42 @@ expect@^24.9.0: jest-message-util "^24.9.0" jest-regex-util "^24.9.0" +express@4.17.1: + version "4.17.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" + integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== + dependencies: + accepts "~1.3.7" + array-flatten "1.1.1" + body-parser "1.19.0" + content-disposition "0.5.3" + content-type "~1.0.4" + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "~1.1.2" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.5" + qs "6.7.0" + range-parser "~1.2.1" + safe-buffer "5.1.2" + send "0.17.1" + serve-static "1.14.1" + setprototypeof "1.1.1" + statuses "~1.5.0" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + ext-list@^2.0.0: version "2.2.2" resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37" @@ -3913,6 +4188,19 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + find-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" @@ -4023,6 +4311,11 @@ formidable@^1.1.1, formidable@^1.2.0: resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9" integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q== +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -4030,7 +4323,7 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" -fresh@~0.5.2: +fresh@0.5.2, fresh@~0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= @@ -4142,6 +4435,11 @@ get-port@^3.2.0: resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" integrity sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw= +get-port@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" + integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== + get-stream@3.0.0, get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -4484,11 +4782,35 @@ helper-md@^0.2.2: fs-exists-sync "^0.1.0" remarkable "^1.6.2" +highlight.js@^10.7.1: + version "10.7.1" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.1.tgz#a8ec4152db24ea630c90927d6cae2a45f8ecb955" + integrity sha512-S6G97tHGqJ/U8DsXcEdnACbirtbx58Bx9CzIVeYli8OuswCfYI/LsXH2EiGcoGio1KAC3x4mmUwulOllJ2ZyRA== + highlight.js@^9.12.0: version "9.18.5" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.5.tgz#d18a359867f378c138d6819edfc2a8acd5f29825" integrity sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA== +history@^4.9.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" + integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== + dependencies: + "@babel/runtime" "^7.1.2" + loose-envify "^1.2.0" + resolve-pathname "^3.0.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + value-equal "^1.0.1" + +hoist-non-react-statics@^3.1.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + hosted-git-info@^2.1.4: version "2.8.8" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" @@ -4544,6 +4866,17 @@ http-cache-semantics@^4.0.0: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== +http-errors@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + http-errors@1.7.3, http-errors@~1.7.2: version "1.7.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" @@ -4750,6 +5083,27 @@ invert-kv@^2.0.0: resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== +ioredis@^4.22.0: + version "4.24.4" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.24.4.tgz#2f3cc03c5bfc75e13141459418bb1bd64ba1c7cf" + integrity sha512-v28xxBENyTmReC6lVTL7EkAPjVF8cuGtDEjGDi1Z2n7htsC2WdiDceZrCIPeuPiLa6kDFWHb1Y8O0upZROsMgA== + dependencies: + cluster-key-slot "^1.1.0" + debug "^4.3.1" + denque "^1.1.0" + lodash.defaults "^4.2.0" + lodash.flatten "^4.4.0" + p-map "^2.1.0" + redis-commands "1.7.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -4930,6 +5284,14 @@ is-installed-globally@^0.3.1: global-dirs "^2.0.1" is-path-inside "^3.0.1" +is-nan@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" + integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + is-natural-number@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" @@ -5848,6 +6210,11 @@ koa-compress@4.0.1: koa-is-json "^1.0.0" statuses "^2.0.0" +koa-connect@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/koa-connect/-/koa-connect-2.1.0.tgz#16bce0a917c4cb24233aaac83fbc5b83804b4a1c" + integrity sha512-O9pcFafHk0oQsBevlbTBlB9co+2RUQJ4zCzu3qJPmGlGoeEZkne+7gWDkecqDPSbCtED6LmhlQladxs6NjOnMQ== + koa-convert@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/koa-convert/-/koa-convert-1.2.0.tgz#da40875df49de0539098d1700b50820cebcd21d0" @@ -6186,6 +6553,16 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" @@ -6286,7 +6663,7 @@ lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.3: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== -lodash@^4.17.19, lodash@^4.17.20: +lodash@^4.17.11, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -6320,7 +6697,7 @@ logging-helpers@^1.0.0: isobject "^3.0.0" log-utils "^0.2.1" -loose-envify@^1.0.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -6469,12 +6846,17 @@ memory-pager@^1.0.2: resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -methods@^1.0.1, methods@^1.1.1, methods@^1.1.2: +methods@^1.0.1, methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= @@ -6542,7 +6924,7 @@ mime-types@^2.1.24: dependencies: mime-db "1.45.0" -mime@^1.3.4, mime@^1.4.1: +mime@1.6.0, mime@^1.3.4, mime@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -6574,6 +6956,14 @@ min-document@^2.19.0: dependencies: dom-walk "^0.1.0" +mini-create-react-context@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz#072171561bfdc922da08a60c2197a497cc2d1d5e" + integrity sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ== + dependencies: + "@babel/runtime" "^7.12.1" + tiny-warning "^1.0.3" + "minimatch@2 || 3", minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -6606,7 +6996,14 @@ mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.4: dependencies: minimist "^1.2.5" -moment@^2.18.1: +moment-timezone@^0.5.31: + version "0.5.33" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.33.tgz#b252fd6bb57f341c9b59a5ab61a8e51a73bbd22c" + integrity sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w== + dependencies: + moment ">= 2.9.0" + +"moment@>= 2.9.0", moment@^2.18.1: version "2.29.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== @@ -6639,6 +7036,11 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + ms@2.1.2, ms@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -6897,7 +7299,7 @@ object-assign@^2.0.0: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa" integrity sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo= -object-assign@^4.0.1, object-assign@^4.1.0: +object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -6967,7 +7369,7 @@ omggif@^1.0.10, omggif@^1.0.9: resolved "https://registry.yarnpkg.com/omggif/-/omggif-1.0.10.tgz#ddaaf90d4a42f532e9e7cb3a95ecdd47f17c7b19" integrity sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw== -on-finished@^2.3.0: +on-finished@^2.3.0, on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= @@ -7097,6 +7499,11 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" +p-map@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" + integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== + p-reduce@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" @@ -7109,6 +7516,13 @@ p-timeout@^2.0.1: dependencies: p-finally "^1.0.0" +p-timeout@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -7192,7 +7606,7 @@ parse5@4.0.0: resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== -parseurl@^1.3.2: +parseurl@^1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== @@ -7222,17 +7636,17 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== -path-to-regexp@^1.1.1: +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + +path-to-regexp@^1.1.1, path-to-regexp@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== @@ -7680,6 +8094,11 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= +pretty-bytes@5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== + pretty-format@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9" @@ -7715,6 +8134,15 @@ progress@^2.0.0, progress@^2.0.3: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +promise.prototype.finally@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.2.tgz#b8af89160c9c673cefe3b4c4435b53cfd0287067" + integrity sha512-A2HuJWl2opDH0EafgdjwEw7HysI8ff/n4lW4QEVBCUXFk9QeGecBWv0Deph0UmLe3tTNYegz8MOjsVuE6SMoJA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.0" + function-bind "^1.1.1" + prompts@^2.0.1: version "2.4.0" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.0.tgz#4aa5de0723a231d1ee9121c40fdf663df73f61d7" @@ -7723,11 +8151,28 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" +prop-types@^15.6.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= +proxy-addr@~2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" + integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.9.1" + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -7783,6 +8228,11 @@ q@^1.1.2: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= +qs@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== + qs@^6.4.0, qs@^6.5.1: version "6.9.4" resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" @@ -7833,6 +8283,21 @@ quill@^1.3.7: parchment "^1.1.4" quill-delta "^3.6.2" +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" + integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== + dependencies: + bytes "3.1.0" + http-errors "1.7.2" + iconv-lite "0.4.24" + unpipe "1.0.0" + raw-body@^2.2.0: version "2.4.1" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c" @@ -7853,11 +8318,64 @@ rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-is@^16.8.4: +react-dom@17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.1.tgz#1de2560474ec9f0e334285662ede52dbc5426fc6" + integrity sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + scheduler "^0.20.1" + +react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-router-dom@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662" + integrity sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + loose-envify "^1.3.1" + prop-types "^15.6.2" + react-router "5.2.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-router@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293" + integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + hoist-non-react-statics "^3.1.0" + loose-envify "^1.3.1" + mini-create-react-context "^0.4.0" + path-to-regexp "^1.7.0" + prop-types "^15.6.2" + react-is "^16.6.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-toastify@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-7.0.3.tgz#93804c777ecf918872ba3b5be9c654db14547f85" + integrity sha512-cxZ5rfurC8LzcZQMTYc8RHIkQTs+BFur18Pzk6Loz6uS8OXUWm6nXVlH/wqglz4Z7UAE8xxcF5mRjfE13487uQ== + dependencies: + clsx "^1.1.1" + +react@17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127" + integrity sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + read-config-file@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/read-config-file/-/read-config-file-6.0.0.tgz#224b5dca6a5bdc1fb19e63f89f342680efdb9299" @@ -7996,6 +8514,30 @@ recast@^0.11.17: private "~0.1.5" source-map "~0.5.0" +redis-commands@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89" + integrity sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ== + +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= + +redis-info@^3.0.8: + version "3.0.8" + resolved "https://registry.yarnpkg.com/redis-info/-/redis-info-3.0.8.tgz#27e696778c100d716324fa68b92cccf390487e02" + integrity sha512-L7yPuGzRq+gu+ZYl/aO0TDgc4nNcMpDTaTN4P3bBi8ZENp1fk8gvtZQpidrYL5uAJYMIcMN81fgUz28qUpTeVA== + dependencies: + lodash "^4.17.11" + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= + dependencies: + redis-errors "^1.0.0" + regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4: version "0.13.7" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" @@ -8166,6 +8708,11 @@ resolve-path@^1.4.0: http-errors "~1.6.2" path-is-absolute "1.0.1" +resolve-pathname@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" + integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -8320,6 +8867,14 @@ sax@>=0.6.0, sax@^1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== +scheduler@^0.20.1: + version "0.20.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + secure-json-parse@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.1.0.tgz#ae76f5624256b5c497af887090a5d9e156c9fb20" @@ -8371,6 +8926,25 @@ semver@^7.3.2: resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + serialize-error@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" @@ -8378,6 +8952,16 @@ serialize-error@^7.0.1: dependencies: type-fest "^0.13.1" +serve-static@1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + server-destroy@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/server-destroy/-/server-destroy-1.0.1.tgz#f13bf928e42b9c3e79383e61cc3998b5d14e6cdd" @@ -8427,23 +9011,11 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - shell-env@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/shell-env/-/shell-env-0.3.0.tgz#2250339022989165bda4eb7bf383afeaaa92dc34" @@ -8725,6 +9297,11 @@ stack-utils@^1.0.1: dependencies: escape-string-regexp "^2.0.0" +standard-as-callback@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" + integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== + stat-mode@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-1.0.0.tgz#68b55cb61ea639ff57136f36b216a291800d1465" @@ -8738,7 +9315,7 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@^1.5.0: +"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@^1.5.0, statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= @@ -9222,11 +9799,21 @@ timm@^1.6.1: resolved "https://registry.yarnpkg.com/timm/-/timm-1.7.1.tgz#96bab60c7d45b5a10a8a4d0f0117c6b7e5aff76f" integrity sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw== +tiny-invariant@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" + integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== + tiny-queue@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/tiny-queue/-/tiny-queue-0.2.1.tgz#25a67f2c6e253b2ca941977b5ef7442ef97a6046" integrity sha1-JaZ/LG4lOyypQZd7XvdELvl6YEY= +tiny-warning@^1.0.0, tiny-warning@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + tinycolor2@^1.4.1: version "1.4.2" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" @@ -9419,7 +10006,7 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-is@^1.6.14, type-is@^1.6.16: +type-is@^1.6.14, type-is@^1.6.16, type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== @@ -9518,7 +10105,7 @@ universalify@^2.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== -unpipe@1.0.0: +unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= @@ -9618,7 +10205,7 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -util.promisify@^1.0.0: +util.promisify@^1.0.0, util.promisify@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.1.1.tgz#77832f57ced2c9478174149cae9b96e9918cd54b" integrity sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw== @@ -9629,6 +10216,11 @@ util.promisify@^1.0.0: has-symbols "^1.0.1" object.getownpropertydescriptors "^2.1.1" +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + uuid@3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" @@ -9649,6 +10241,11 @@ uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^8.3.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + v8-compile-cache@^2.0.3: version "2.1.1" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" @@ -9667,7 +10264,12 @@ validate.js@0.13.1: resolved "https://registry.yarnpkg.com/validate.js/-/validate.js-0.13.1.tgz#b58bfac04a0f600a340f62e5227e70d95971e92a" integrity sha512-PnFM3xiZ+kYmLyTiMgTYmU7ZHkjBZz2/+F0DaALc/uUtVzdCt1wAosvYJ5hFQi/hz8O4zb52FQhHZRC+uVkJ+g== -vary@^1.1.2: +value-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" + integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== + +vary@^1.1.2, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= @@ -9763,13 +10365,6 @@ which@^1.2.9, which@^1.3.0: dependencies: isexe "^2.0.0" -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - widest-line@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" diff --git a/packages/worker/package.json b/packages/worker/package.json index 1670fd729e..7d88654a88 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -33,6 +33,5 @@ "pouchdb": "^7.2.2", "pouchdb-all-dbs": "^1.0.2", "server-destroy": "^1.0.1" - }, - "gitHead": "1b95326b20d1352d36305910259228b96a683dc7" + } } diff --git a/packages/worker/src/db/index.js b/packages/worker/src/db/index.js index 24bbb55092..83f030c846 100644 --- a/packages/worker/src/db/index.js +++ b/packages/worker/src/db/index.js @@ -1,12 +1,9 @@ const PouchDB = require("pouchdb") const allDbs = require("pouchdb-all-dbs") const env = require("../environment") -const { join } = require("path") -const { homedir } = require("os") // level option is purely for testing (development) -const COUCH_DB_URL = - env.COUCH_DB_URL || `leveldb://${join(homedir(), ".budibase")}/.data/` +const COUCH_DB_URL = env.COUCH_DB_URL || "http://localhost:10000/db/" const Pouch = PouchDB.defaults({ prefix: COUCH_DB_URL,