From 292d520b30a7ab887c3054d61dc02bde8d53741a Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 24 May 2021 14:54:47 +0100 Subject: [PATCH 01/18] Adding some changes for to redis library, allowing reconnection. --- hosting/docker-compose.yaml | 2 + packages/auth/src/redis/index.js | 95 +++++++++++-------- packages/worker/src/api/routes/admin/users.js | 9 +- 3 files changed, 68 insertions(+), 38 deletions(-) diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index 37657ce00..0cd7bc92b 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -105,6 +105,8 @@ services: restart: always image: redis command: redis-server --requirepass ${REDIS_PASSWORD} + ports: + - "${REDIS_PORT}:6379" volumes: - redis_data:/data diff --git a/packages/auth/src/redis/index.js b/packages/auth/src/redis/index.js index 78e3ea7ac..0fd0aa8c8 100644 --- a/packages/auth/src/redis/index.js +++ b/packages/auth/src/redis/index.js @@ -3,40 +3,64 @@ const env = require("../environment") const Redis = env.isTest() ? require("ioredis-mock") : require("ioredis") const { addDbPrefix, removeDbPrefix, getRedisOptions } = require("./utils") +const RETRY_PERIOD_MS = 2000 +const MAX_RETRIES = 20 const CLUSTERED = false // for testing just generate the client once let CONNECTED = false let CLIENT = env.isTest() ? new Redis(getRedisOptions()) : null +function retryConnection() { + setTimeout(init, RETRY_PERIOD_MS) +} + /** * Inits the system, will error if unable to connect to redis cluster (may take up to 10 seconds) otherwise * will return the ioredis client which will be ready to use. - * @return {Promise} The ioredis client. */ function init() { - return new Promise((resolve, reject) => { - // testing uses a single in memory client - if (env.isTest() || (CLIENT && CONNECTED)) { - return resolve(CLIENT) - } - const { opts, host, port } = getRedisOptions(CLUSTERED) - if (CLUSTERED) { - CLIENT = new Redis.Cluster([{ host, port }], opts) - } else { - CLIENT = new Redis(opts) + function errorOccurred(err) { + CONNECTED = false; + console.error("Redis connection failed - " + err) + setTimeout(() => { + init() + }, RETRY_PERIOD_MS) + } + // testing uses a single in memory client + if (env.isTest() || (CLIENT && CONNECTED)) { + return + } + if (CLIENT) { + CLIENT.disconnect() + } + const { opts, host, port } = getRedisOptions(CLUSTERED) + if (CLUSTERED) { + CLIENT = new Redis.Cluster([{ host, port }], opts) + } else { + CLIENT = new Redis(opts) + } + CLIENT.on("end", err => { + errorOccurred(err) + }) + CLIENT.on("error", err => { + errorOccurred(err) + }) + CLIENT.on("connect", () => { + CONNECTED = true + }) +} + +function waitForConnection() { + return new Promise(resolve => { + if (CLIENT == null) { + init() + } else if (CONNECTED) { + resolve() + return } - CLIENT.on("end", err => { - reject(err) - CONNECTED = false - }) - CLIENT.on("error", err => { - reject(err) - CONNECTED = false - }) CLIENT.on("connect", () => { - resolve(CLIENT) - CONNECTED = true + resolve() }) }) } @@ -85,31 +109,30 @@ class RedisWrapper { } async init() { - this._client = await init() + init() + await waitForConnection() return this } async finish() { - this._client.disconnect() + CLIENT.disconnect() } async scan() { - const db = this._db, - client = this._client + const db = this._db let stream if (CLUSTERED) { - let node = client.nodes("master") + let node = CLIENT.nodes("master") stream = node[0].scanStream({ match: db + "-*", count: 100 }) } else { - stream = client.scanStream({ match: db + "-*", count: 100 }) + stream = CLIENT.scanStream({ match: db + "-*", count: 100 }) } return promisifyStream(stream) } async get(key) { - const db = this._db, - client = this._client - let response = await client.get(addDbPrefix(db, key)) + const db = this._db + let response = await CLIENT.get(addDbPrefix(db, key)) // overwrite the prefixed key if (response != null && response.key) { response.key = key @@ -123,22 +146,20 @@ class RedisWrapper { } async store(key, value, expirySeconds = null) { - const db = this._db, - client = this._client + const db = this._db if (typeof value === "object") { value = JSON.stringify(value) } const prefixedKey = addDbPrefix(db, key) - await client.set(prefixedKey, value) + await CLIENT.set(prefixedKey, value) if (expirySeconds) { - await client.expire(prefixedKey, expirySeconds) + await CLIENT.expire(prefixedKey, expirySeconds) } } async delete(key) { - const db = this._db, - client = this._client - await client.del(addDbPrefix(db, key)) + const db = this._db + await CLIENT.del(addDbPrefix(db, key)) } async clear() { diff --git a/packages/worker/src/api/routes/admin/users.js b/packages/worker/src/api/routes/admin/users.js index f334f05e7..cfff67f3e 100644 --- a/packages/worker/src/api/routes/admin/users.js +++ b/packages/worker/src/api/routes/admin/users.js @@ -6,6 +6,13 @@ const Joi = require("joi") const router = Router() +function buildAdminInitValidation() { + return joiValidator.body(Joi.object({ + email: Joi.string().required(), + password: Joi.string().required(), + }).required().unknown(false)) +} + function buildUserSaveValidation(isSelf = false) { let schema = { email: Joi.string().allow(null, ""), @@ -74,7 +81,7 @@ router buildInviteAcceptValidation(), controller.inviteAccept ) - .post("/api/admin/users/init", controller.adminUser) + .post("/api/admin/users/init", buildAdminInitValidation(), controller.adminUser) .get("/api/admin/users/self", controller.getSelf) // admin endpoint but needs to come at end (blocks other endpoints otherwise) .get("/api/admin/users/:id", adminOnly, controller.find) From 428a9e5ba3266abcda59d7f49cc89ef32e4ec83c Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 24 May 2021 14:56:23 +0100 Subject: [PATCH 02/18] Formatting. --- packages/auth/src/redis/index.js | 2 +- packages/worker/src/api/routes/admin/users.js | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/auth/src/redis/index.js b/packages/auth/src/redis/index.js index 0fd0aa8c8..7a1b9a343 100644 --- a/packages/auth/src/redis/index.js +++ b/packages/auth/src/redis/index.js @@ -21,7 +21,7 @@ function retryConnection() { */ function init() { function errorOccurred(err) { - CONNECTED = false; + CONNECTED = false console.error("Redis connection failed - " + err) setTimeout(() => { init() diff --git a/packages/worker/src/api/routes/admin/users.js b/packages/worker/src/api/routes/admin/users.js index cfff67f3e..eff873a7b 100644 --- a/packages/worker/src/api/routes/admin/users.js +++ b/packages/worker/src/api/routes/admin/users.js @@ -7,10 +7,14 @@ const Joi = require("joi") const router = Router() function buildAdminInitValidation() { - return joiValidator.body(Joi.object({ - email: Joi.string().required(), - password: Joi.string().required(), - }).required().unknown(false)) + return joiValidator.body( + Joi.object({ + email: Joi.string().required(), + password: Joi.string().required(), + }) + .required() + .unknown(false) + ) } function buildUserSaveValidation(isSelf = false) { @@ -81,7 +85,11 @@ router buildInviteAcceptValidation(), controller.inviteAccept ) - .post("/api/admin/users/init", buildAdminInitValidation(), controller.adminUser) + .post( + "/api/admin/users/init", + buildAdminInitValidation(), + controller.adminUser + ) .get("/api/admin/users/self", controller.getSelf) // admin endpoint but needs to come at end (blocks other endpoints otherwise) .get("/api/admin/users/:id", adminOnly, controller.find) From 8ce6617e19e366728ecea6ce28e4e367f3219c63 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 24 May 2021 14:58:54 +0100 Subject: [PATCH 03/18] Logging and adding better messaging around startup. --- packages/cli/src/hosting/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/hosting/index.js b/packages/cli/src/hosting/index.js index 60d9f13e8..159f70548 100644 --- a/packages/cli/src/hosting/index.js +++ b/packages/cli/src/hosting/index.js @@ -101,10 +101,11 @@ async function init(type) { async function start() { await checkDockerConfigured() checkInitComplete() - console.log(info("Starting services, this may take a moment.")) + console.log(info("Starting services, this may take a moment - first time this may take a few minutes to download images.")) const port = makeEnv.get("MAIN_PORT") await handleError(async () => { - await compose.upAll({ cwd: "./", log: false }) + // need to log as it makes it more clear + await compose.upAll({ cwd: "./", log: true }) }) console.log( success( From 2abe543cb18c45d574dffe0ce0cc72d96fa3d530 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 24 May 2021 16:20:02 +0100 Subject: [PATCH 04/18] Linting and fixing an issue with the dev pass through. --- packages/auth/src/redis/index.js | 5 ----- packages/server/src/api/controllers/dev.js | 9 +++++++++ packages/worker/src/api/controllers/admin/configs.js | 9 +-------- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/packages/auth/src/redis/index.js b/packages/auth/src/redis/index.js index 7a1b9a343..0a2196630 100644 --- a/packages/auth/src/redis/index.js +++ b/packages/auth/src/redis/index.js @@ -4,17 +4,12 @@ const Redis = env.isTest() ? require("ioredis-mock") : require("ioredis") const { addDbPrefix, removeDbPrefix, getRedisOptions } = require("./utils") const RETRY_PERIOD_MS = 2000 -const MAX_RETRIES = 20 const CLUSTERED = false // for testing just generate the client once let CONNECTED = false let CLIENT = env.isTest() ? new Redis(getRedisOptions()) : null -function retryConnection() { - setTimeout(init, RETRY_PERIOD_MS) -} - /** * Inits the system, will error if unable to connect to redis cluster (may take up to 10 seconds) otherwise * will return the ioredis client which will be ready to use. diff --git a/packages/server/src/api/controllers/dev.js b/packages/server/src/api/controllers/dev.js index 2e90fb83e..068e1e59c 100644 --- a/packages/server/src/api/controllers/dev.js +++ b/packages/server/src/api/controllers/dev.js @@ -23,7 +23,16 @@ async function redirect(ctx, method) { if (cookie) { ctx.set("set-cookie", cookie) } + let body + try { + body = await response.json() + } catch (err) { + // don't worry about errors, likely no JSON + } ctx.status = response.status + if (body) { + ctx.body = body + } ctx.cookies } diff --git a/packages/worker/src/api/controllers/admin/configs.js b/packages/worker/src/api/controllers/admin/configs.js index 8a6788cdf..82466249a 100644 --- a/packages/worker/src/api/controllers/admin/configs.js +++ b/packages/worker/src/api/controllers/admin/configs.js @@ -6,10 +6,8 @@ const { getGlobalUserParams, getScopedFullConfig, } = require("@budibase/auth").db -const fetch = require("node-fetch") const { Configs } = require("../../../constants") const email = require("../../../utilities/email") -const env = require("../../../environment") const { upload, ObjectStoreBuckets } = require("@budibase/auth").objectStore const APP_PREFIX = "app_" @@ -155,12 +153,7 @@ exports.configChecklist = async function (ctx) { // TODO: Watch get started video // Apps exist - let allDbs - if (env.COUCH_DB_URL) { - allDbs = await (await fetch(`${env.COUCH_DB_URL}/_all_dbs`)).json() - } else { - allDbs = await CouchDB.allDbs() - } + let allDbs = await CouchDB.allDbs() const appDbNames = allDbs.filter(dbName => dbName.startsWith(APP_PREFIX)) // They have set up SMTP From 68681fd441ac36fdcc7cd5b24739f9add4a2bcc6 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 24 May 2021 16:20:28 +0100 Subject: [PATCH 05/18] Formatting. --- packages/cli/src/hosting/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/hosting/index.js b/packages/cli/src/hosting/index.js index 159f70548..05d221435 100644 --- a/packages/cli/src/hosting/index.js +++ b/packages/cli/src/hosting/index.js @@ -101,7 +101,11 @@ async function init(type) { async function start() { await checkDockerConfigured() checkInitComplete() - console.log(info("Starting services, this may take a moment - first time this may take a few minutes to download images.")) + console.log( + info( + "Starting services, this may take a moment - first time this may take a few minutes to download images." + ) + ) const port = makeEnv.get("MAIN_PORT") await handleError(async () => { // need to log as it makes it more clear From ec85f13d5a146535abaa7965c0d2d42f3f0d14e0 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 24 May 2021 16:30:24 +0100 Subject: [PATCH 06/18] Adding an initial connection timeout of 5 seconds which after it will retry again. --- packages/auth/src/redis/index.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/auth/src/redis/index.js b/packages/auth/src/redis/index.js index 0a2196630..a3cb6385e 100644 --- a/packages/auth/src/redis/index.js +++ b/packages/auth/src/redis/index.js @@ -4,6 +4,7 @@ const Redis = env.isTest() ? require("ioredis-mock") : require("ioredis") const { addDbPrefix, removeDbPrefix, getRedisOptions } = require("./utils") const RETRY_PERIOD_MS = 2000 +const STARTUP_TIMEOUT_MS = 5000 const CLUSTERED = false // for testing just generate the client once @@ -15,7 +16,10 @@ let CLIENT = env.isTest() ? new Redis(getRedisOptions()) : null * will return the ioredis client which will be ready to use. */ function init() { + let timeout function errorOccurred(err) { + // always clear this on error + clearTimeout(timeout) CONNECTED = false console.error("Redis connection failed - " + err) setTimeout(() => { @@ -26,6 +30,14 @@ function init() { if (env.isTest() || (CLIENT && CONNECTED)) { return } + // start the timer - only allowed 5 seconds to connect + timeout = setTimeout(() => { + if (!CONNECTED) { + errorOccurred() + } + }, STARTUP_TIMEOUT_MS) + + // disconnect any lingering client if (CLIENT) { CLIENT.disconnect() } @@ -35,6 +47,7 @@ function init() { } else { CLIENT = new Redis(opts) } + // attach handlers CLIENT.on("end", err => { errorOccurred(err) }) @@ -42,6 +55,7 @@ function init() { errorOccurred(err) }) CLIENT.on("connect", () => { + clearTimeout(timeout) CONNECTED = true }) } From bd0f78e38ea93736f5de7d7436ff03d63033f066 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 24 May 2021 16:31:50 +0100 Subject: [PATCH 07/18] Changing how connection is waited for. --- packages/auth/src/redis/index.js | 10 +++++++--- packages/builder/cypress/support/commands.js | 19 +++++++++++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/packages/auth/src/redis/index.js b/packages/auth/src/redis/index.js index a3cb6385e..044a8bcaf 100644 --- a/packages/auth/src/redis/index.js +++ b/packages/auth/src/redis/index.js @@ -68,9 +68,13 @@ function waitForConnection() { resolve() return } - CLIENT.on("connect", () => { - resolve() - }) + // check if the connection is ready + const interval = setInterval(() => { + if (CONNECTED) { + clearInterval(interval) + resolve() + } + }, 500) }) } diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js index 4f759a60e..62365cbe8 100644 --- a/packages/builder/cypress/support/commands.js +++ b/packages/builder/cypress/support/commands.js @@ -11,12 +11,23 @@ Cypress.Commands.add("login", () => { if (cookie) return cy.visit(`localhost:${Cypress.env("PORT")}/builder`) - cy.contains("Create Test User").click() - cy.get("input").first().type("test@test.com") - cy.get('input[type="password"]').type("test") + cy.get("button").then(btn => { + const adminUserButton = "Create super admin user" + console.log(btn.first().first()) + if (!btn.first().contains(adminUserButton)) { + // create admin user + cy.get("input").first().type("test@test.com") + cy.get('input[type="password"]').first().type("test") + cy.get('input[type="password"]').eq(1).type("test") + cy.contains(adminUserButton).click() + } - cy.contains("Login").click() + // login + cy.get("input").first().type("test@test.com") + cy.get('input[type="password"]').type("test") + cy.contains("Login").click() + }) }) }) From 4268ea8eb0551f58fa7161134fdead8ea30d917c Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 24 May 2021 16:32:42 +0100 Subject: [PATCH 08/18] Changing cypress commands. --- packages/builder/cypress/support/commands.js | 32 ++++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js index 62365cbe8..80d38937a 100644 --- a/packages/builder/cypress/support/commands.js +++ b/packages/builder/cypress/support/commands.js @@ -12,22 +12,22 @@ Cypress.Commands.add("login", () => { cy.visit(`localhost:${Cypress.env("PORT")}/builder`) - cy.get("button").then(btn => { - const adminUserButton = "Create super admin user" - console.log(btn.first().first()) - if (!btn.first().contains(adminUserButton)) { - // create admin user - cy.get("input").first().type("test@test.com") - cy.get('input[type="password"]').first().type("test") - cy.get('input[type="password"]').eq(1).type("test") - cy.contains(adminUserButton).click() - } - - // login - cy.get("input").first().type("test@test.com") - cy.get('input[type="password"]').type("test") - cy.contains("Login").click() - }) + // cy.get("button").then(btn => { + // const adminUserButton = "Create super admin user" + // console.log(btn.first().first()) + // if (!btn.first().contains(adminUserButton)) { + // // create admin user + // cy.get("input").first().type("test@test.com") + // cy.get('input[type="password"]').first().type("test") + // cy.get('input[type="password"]').eq(1).type("test") + // cy.contains(adminUserButton).click() + // } + + // login + cy.get("input").first().type("test@test.com") + cy.get('input[type="password"]').type("test") + cy.contains("Login").click() + // }) }) }) From 8200f2a4e8aa663b0ccd67b1bbaa0c136a9c972a Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 24 May 2021 17:05:46 +0100 Subject: [PATCH 09/18] Fixing issue with redis updates in tests. --- packages/auth/src/redis/index.js | 36 ++++++++++++++++---------- packages/server/src/app.js | 3 ++- packages/server/src/utilities/redis.js | 5 ++++ 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/packages/auth/src/redis/index.js b/packages/auth/src/redis/index.js index 044a8bcaf..7d9e9ad63 100644 --- a/packages/auth/src/redis/index.js +++ b/packages/auth/src/redis/index.js @@ -8,8 +8,24 @@ const STARTUP_TIMEOUT_MS = 5000 const CLUSTERED = false // for testing just generate the client once -let CONNECTED = false +let CLOSED = false let CLIENT = env.isTest() ? new Redis(getRedisOptions()) : null +// if in test always connected +let CONNECTED = !!env.isTest() + +function connectionError(timeout, err) { + // manually shut down, ignore errors + if (CLOSED) { + return + } + // always clear this on error + clearTimeout(timeout) + CONNECTED = false + console.error("Redis connection failed - " + err) + setTimeout(() => { + init() + }, RETRY_PERIOD_MS) +} /** * Inits the system, will error if unable to connect to redis cluster (may take up to 10 seconds) otherwise @@ -17,15 +33,7 @@ let CLIENT = env.isTest() ? new Redis(getRedisOptions()) : null */ function init() { let timeout - function errorOccurred(err) { - // always clear this on error - clearTimeout(timeout) - CONNECTED = false - console.error("Redis connection failed - " + err) - setTimeout(() => { - init() - }, RETRY_PERIOD_MS) - } + CLOSED = false // testing uses a single in memory client if (env.isTest() || (CLIENT && CONNECTED)) { return @@ -33,7 +41,7 @@ function init() { // start the timer - only allowed 5 seconds to connect timeout = setTimeout(() => { if (!CONNECTED) { - errorOccurred() + connectionError(timeout) } }, STARTUP_TIMEOUT_MS) @@ -49,10 +57,10 @@ function init() { } // attach handlers CLIENT.on("end", err => { - errorOccurred(err) + connectionError(timeout, err) }) CLIENT.on("error", err => { - errorOccurred(err) + connectionError(timeout, err) }) CLIENT.on("connect", () => { clearTimeout(timeout) @@ -122,12 +130,14 @@ class RedisWrapper { } async init() { + CLOSED = false init() await waitForConnection() return this } async finish() { + CLOSED = true CLIENT.disconnect() } diff --git a/packages/server/src/app.js b/packages/server/src/app.js index 9ec6c2c68..50df056b2 100644 --- a/packages/server/src/app.js +++ b/packages/server/src/app.js @@ -73,10 +73,11 @@ if (env.isProd()) { const server = http.createServer(app.callback()) destroyable(server) -server.on("close", () => { +server.on("close", async () => { if (env.NODE_ENV !== "jest") { console.log("Server Closed") } + await redis.shutdown() }) module.exports = server.listen(env.PORT || 0, async () => { diff --git a/packages/server/src/utilities/redis.js b/packages/server/src/utilities/redis.js index 8e0f774f4..ae18b82e0 100644 --- a/packages/server/src/utilities/redis.js +++ b/packages/server/src/utilities/redis.js @@ -11,6 +11,11 @@ exports.init = async () => { debounceClient = await new Client(utils.Databases.DEBOUNCE).init() } +exports.shutdown = async () => { + await devAppClient.finish() + await debounceClient.finish() +} + exports.doesUserHaveLock = async (devAppId, user) => { const value = await devAppClient.get(devAppId) if (!value) { From a2917e3ffd3e92ae99c0034817895f1e553de1ba Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Mon, 24 May 2021 17:20:20 +0100 Subject: [PATCH 10/18] v0.9.2 --- lerna.json | 2 +- packages/auth/package.json | 2 +- packages/bbui/package.json | 2 +- packages/builder/package.json | 8 ++++---- packages/cli/package.json | 2 +- packages/client/package.json | 6 +++--- packages/server/package.json | 10 +++++----- packages/standard-components/package.json | 4 ++-- packages/string-templates/package.json | 2 +- packages/worker/package.json | 6 +++--- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lerna.json b/lerna.json index fa5307c01..0117b31b8 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.9.1", + "version": "0.9.2", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/auth/package.json b/packages/auth/package.json index d8003df42..2717c8fff 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/auth", - "version": "0.9.1", + "version": "0.9.2", "description": "Authentication middlewares for budibase builder and apps", "main": "src/index.js", "author": "Budibase", diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 1d05abcc1..66dffd5d8 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "0.9.1", + "version": "0.9.2", "license": "AGPL-3.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/builder/package.json b/packages/builder/package.json index acc8c71a1..8d9124c53 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.9.1", + "version": "0.9.2", "license": "AGPL-3.0", "private": true, "scripts": { @@ -65,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^0.9.1", - "@budibase/client": "^0.9.1", + "@budibase/bbui": "^0.9.2", + "@budibase/client": "^0.9.2", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^0.9.1", + "@budibase/string-templates": "^0.9.2", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index ab3766c3e..9a1b64119 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "0.9.1", + "version": "0.9.2", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { diff --git a/packages/client/package.json b/packages/client/package.json index b4ab3754a..aac83671c 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "0.9.1", + "version": "0.9.2", "license": "MPL-2.0", "module": "dist/budibase-client.js", "main": "dist/budibase-client.js", @@ -18,13 +18,13 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/string-templates": "^0.9.1", + "@budibase/string-templates": "^0.9.2", "regexparam": "^1.3.0", "shortid": "^2.2.15", "svelte-spa-router": "^3.0.5" }, "devDependencies": { - "@budibase/standard-components": "^0.9.1", + "@budibase/standard-components": "^0.9.2", "@rollup/plugin-commonjs": "^18.0.0", "@rollup/plugin-node-resolve": "^11.2.1", "fs-extra": "^8.1.0", diff --git a/packages/server/package.json b/packages/server/package.json index 2af49cd91..9f2e48767 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "0.9.1", + "version": "0.9.2", "description": "Budibase Web Server", "main": "src/electron.js", "repository": { @@ -81,9 +81,9 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.1", - "@budibase/client": "^0.9.1", - "@budibase/string-templates": "^0.9.1", + "@budibase/auth": "^0.9.2", + "@budibase/client": "^0.9.2", + "@budibase/string-templates": "^0.9.2", "@elastic/elasticsearch": "7.10.0", "@koa/router": "8.0.0", "@sendgrid/mail": "7.1.1", @@ -133,7 +133,7 @@ "zlib": "1.0.5" }, "devDependencies": { - "@budibase/standard-components": "^0.9.1", + "@budibase/standard-components": "^0.9.2", "@jest/test-sequencer": "^24.8.0", "docker-compose": "^0.23.6", "eslint": "^6.8.0", diff --git a/packages/standard-components/package.json b/packages/standard-components/package.json index d85097aa2..1a3677082 100644 --- a/packages/standard-components/package.json +++ b/packages/standard-components/package.json @@ -29,11 +29,11 @@ "keywords": [ "svelte" ], - "version": "0.9.1", + "version": "0.9.2", "license": "MIT", "gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc", "dependencies": { - "@budibase/bbui": "^0.9.1", + "@budibase/bbui": "^0.9.2", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", "apexcharts": "^3.22.1", diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 8ea3663d4..296ae1837 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "0.9.1", + "version": "0.9.2", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/worker/package.json b/packages/worker/package.json index ac59d7450..9ecb58ece 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "0.9.1", + "version": "0.9.2", "description": "Budibase background service", "main": "src/index.js", "repository": { @@ -21,8 +21,8 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.1", - "@budibase/string-templates": "^0.9.1", + "@budibase/auth": "^0.9.2", + "@budibase/string-templates": "^0.9.2", "@koa/router": "^8.0.0", "aws-sdk": "^2.811.0", "bcryptjs": "^2.4.3", From d89c7507611365b11944c31804d06dd9376ca2c1 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 24 May 2021 18:45:43 +0100 Subject: [PATCH 11/18] Updating system to allow setting builder/admin as a toggle during the invitation phase of a user. --- .../users/_components/AddUserModal.svelte | 25 +++++++++++-- .../_components/BasicOnboardingModal.svelte | 32 +++++++++++++++-- packages/builder/src/stores/portal/users.js | 35 ++++++++++++++----- .../worker/src/api/controllers/admin/users.js | 7 ++-- packages/worker/src/api/routes/admin/users.js | 1 + packages/worker/src/index.js | 9 ++++- packages/worker/src/utilities/email.js | 9 ++--- packages/worker/src/utilities/redis.js | 33 +++++++++++++---- 8 files changed, 124 insertions(+), 27 deletions(-) diff --git a/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte b/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte index b60f15d97..9504f73b6 100644 --- a/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte +++ b/packages/builder/src/pages/builder/portal/manage/users/_components/AddUserModal.svelte @@ -5,6 +5,8 @@ Select, ModalContent, notifications, + Toggle, + Label, } from "@budibase/bbui" import { createValidationStore, emailValidator } from "helpers/validation" import { users } from "stores/portal" @@ -13,12 +15,12 @@ const options = ["Email onboarding", "Basic onboarding"] let selected = options[0] + let builder, admin const [email, error, touched] = createValidationStore("", emailValidator) async function createUserFlow() { - const res = await users.invite($email) - console.log(res) + const res = await users.invite({ email: $email, builder, admin }) if (res.status) { notifications.error(res.message) } else { @@ -56,4 +58,23 @@ placeholder="john@doe.com" label="Email" /> +
+
+ + +
+
+ + +
+
+ + diff --git a/packages/builder/src/pages/builder/portal/manage/users/_components/BasicOnboardingModal.svelte b/packages/builder/src/pages/builder/portal/manage/users/_components/BasicOnboardingModal.svelte index 74a696191..8ecb27458 100644 --- a/packages/builder/src/pages/builder/portal/manage/users/_components/BasicOnboardingModal.svelte +++ b/packages/builder/src/pages/builder/portal/manage/users/_components/BasicOnboardingModal.svelte @@ -1,13 +1,22 @@ {#if show} - - -
- google icon -

Sign in with Google

-
-
+ window.open("/api/admin/auth/google", "_blank")} + > +
+ google icon +

Sign in with Google

+
{/if} diff --git a/packages/server/src/utilities/fileSystem/index.js b/packages/server/src/utilities/fileSystem/index.js index d84c6deb8..0e267b361 100644 --- a/packages/server/src/utilities/fileSystem/index.js +++ b/packages/server/src/utilities/fileSystem/index.js @@ -12,8 +12,9 @@ const { streamUpload, deleteFolder, downloadTarball, + uploadDirectory, } = require("./utilities") -const { downloadLibraries, newAppPublicPath } = require("./newApp") +const { downloadLibraries, uploadClientLibrary } = require("./newApp") const download = require("download") const env = require("../../environment") const { homedir } = require("os") @@ -134,7 +135,8 @@ exports.performBackup = async (appId, backupName) => { */ exports.createApp = async appId => { await downloadLibraries(appId) - await newAppPublicPath(appId) + await uploadClientLibrary(appId) + await uploadDirectory(ObjectStoreBuckets.APPS, NODE_MODULES_PATH, appId) } /** diff --git a/packages/server/src/utilities/fileSystem/newApp.js b/packages/server/src/utilities/fileSystem/newApp.js index 91cc77174..22113757c 100644 --- a/packages/server/src/utilities/fileSystem/newApp.js +++ b/packages/server/src/utilities/fileSystem/newApp.js @@ -26,10 +26,9 @@ exports.downloadLibraries = async appId => { return paths } -exports.newAppPublicPath = async appId => { - const path = join(appId, "public") +exports.uploadClientLibrary = async appId => { const sourcepath = require.resolve("@budibase/client") - const destPath = join(path, "budibase-client.js") + const destPath = join(appId, "budibase-client.js") await streamUpload(BUCKET_NAME, destPath, fs.createReadStream(sourcepath)) } From 4e2d8ee6cf5b2c71bf13dbffbfb91217dff61720 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Mon, 24 May 2021 19:54:00 +0100 Subject: [PATCH 13/18] removed node modules upload call --- packages/server/src/utilities/fileSystem/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/server/src/utilities/fileSystem/index.js b/packages/server/src/utilities/fileSystem/index.js index 0e267b361..750414826 100644 --- a/packages/server/src/utilities/fileSystem/index.js +++ b/packages/server/src/utilities/fileSystem/index.js @@ -12,7 +12,6 @@ const { streamUpload, deleteFolder, downloadTarball, - uploadDirectory, } = require("./utilities") const { downloadLibraries, uploadClientLibrary } = require("./newApp") const download = require("download") @@ -136,7 +135,6 @@ exports.performBackup = async (appId, backupName) => { exports.createApp = async appId => { await downloadLibraries(appId) await uploadClientLibrary(appId) - await uploadDirectory(ObjectStoreBuckets.APPS, NODE_MODULES_PATH, appId) } /** From f3198aec980595d44633dd604f55bb48d1a18100 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Mon, 24 May 2021 21:30:46 +0100 Subject: [PATCH 14/18] sanitizing client lib URL --- packages/auth/src/objectStore/index.js | 4 ++++ packages/server/src/utilities/index.js | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/auth/src/objectStore/index.js b/packages/auth/src/objectStore/index.js index c6d1c3e2c..a157332ae 100644 --- a/packages/auth/src/objectStore/index.js +++ b/packages/auth/src/objectStore/index.js @@ -34,11 +34,15 @@ function sanitizeKey(input) { return sanitize(sanitizeBucket(input)).replace(/\\/g, "/") } +exports.sanitizeKey = sanitizeKey + // simply handles the dev app to app conversion function sanitizeBucket(input) { return input.replace(new RegExp(APP_DEV_PREFIX, "g"), APP_PREFIX) } +exports.sanitizeBucket = sanitizeBucket + function publicPolicy(bucketName) { return { Version: "2012-10-17", diff --git a/packages/server/src/utilities/index.js b/packages/server/src/utilities/index.js index 1c85d9c7b..320d4a3eb 100644 --- a/packages/server/src/utilities/index.js +++ b/packages/server/src/utilities/index.js @@ -1,6 +1,7 @@ const env = require("../environment") const { OBJ_STORE_DIRECTORY, ObjectStoreBuckets } = require("../constants") const { getAllApps } = require("@budibase/auth/db") +const { sanitizeKey } = require("@budibase/auth/src/objectStore") const BB_CDN = "https://cdn.app.budi.live/assets" @@ -43,7 +44,9 @@ exports.objectStoreUrl = () => { */ exports.clientLibraryPath = appId => { if (env.isProd()) { - return `${exports.objectStoreUrl()}/${appId}/budibase-client.js` + return `${exports.objectStoreUrl()}/${sanitizeKey( + appId + )}/budibase-client.js` } else { return `/api/assets/client` } From 881ddc646f6ac3da0d95eaae9fea190670210d77 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Mon, 24 May 2021 21:54:28 +0100 Subject: [PATCH 15/18] v0.9.3 --- lerna.json | 2 +- packages/auth/package.json | 2 +- packages/bbui/package.json | 2 +- packages/builder/package.json | 8 ++++---- packages/cli/package.json | 2 +- packages/client/package.json | 6 +++--- packages/server/package.json | 10 +++++----- packages/standard-components/package.json | 4 ++-- packages/string-templates/package.json | 2 +- packages/worker/package.json | 6 +++--- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lerna.json b/lerna.json index 0117b31b8..01ddc677a 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.9.2", + "version": "0.9.3", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/auth/package.json b/packages/auth/package.json index 2717c8fff..29b9ef380 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/auth", - "version": "0.9.2", + "version": "0.9.3", "description": "Authentication middlewares for budibase builder and apps", "main": "src/index.js", "author": "Budibase", diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 66dffd5d8..e30c0821a 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "0.9.2", + "version": "0.9.3", "license": "AGPL-3.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/builder/package.json b/packages/builder/package.json index 8d9124c53..62891c187 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "0.9.2", + "version": "0.9.3", "license": "AGPL-3.0", "private": true, "scripts": { @@ -65,10 +65,10 @@ } }, "dependencies": { - "@budibase/bbui": "^0.9.2", - "@budibase/client": "^0.9.2", + "@budibase/bbui": "^0.9.3", + "@budibase/client": "^0.9.3", "@budibase/colorpicker": "1.1.2", - "@budibase/string-templates": "^0.9.2", + "@budibase/string-templates": "^0.9.3", "@sentry/browser": "5.19.1", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index 9a1b64119..2c41142a0 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/cli", - "version": "0.9.2", + "version": "0.9.3", "description": "Budibase CLI, for developers, self hosting and migrations.", "main": "src/index.js", "bin": { diff --git a/packages/client/package.json b/packages/client/package.json index aac83671c..879332db7 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/client", - "version": "0.9.2", + "version": "0.9.3", "license": "MPL-2.0", "module": "dist/budibase-client.js", "main": "dist/budibase-client.js", @@ -18,13 +18,13 @@ "dev:builder": "rollup -cw" }, "dependencies": { - "@budibase/string-templates": "^0.9.2", + "@budibase/string-templates": "^0.9.3", "regexparam": "^1.3.0", "shortid": "^2.2.15", "svelte-spa-router": "^3.0.5" }, "devDependencies": { - "@budibase/standard-components": "^0.9.2", + "@budibase/standard-components": "^0.9.3", "@rollup/plugin-commonjs": "^18.0.0", "@rollup/plugin-node-resolve": "^11.2.1", "fs-extra": "^8.1.0", diff --git a/packages/server/package.json b/packages/server/package.json index 9f2e48767..e76c4e4e4 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/server", "email": "hi@budibase.com", - "version": "0.9.2", + "version": "0.9.3", "description": "Budibase Web Server", "main": "src/electron.js", "repository": { @@ -81,9 +81,9 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.2", - "@budibase/client": "^0.9.2", - "@budibase/string-templates": "^0.9.2", + "@budibase/auth": "^0.9.3", + "@budibase/client": "^0.9.3", + "@budibase/string-templates": "^0.9.3", "@elastic/elasticsearch": "7.10.0", "@koa/router": "8.0.0", "@sendgrid/mail": "7.1.1", @@ -133,7 +133,7 @@ "zlib": "1.0.5" }, "devDependencies": { - "@budibase/standard-components": "^0.9.2", + "@budibase/standard-components": "^0.9.3", "@jest/test-sequencer": "^24.8.0", "docker-compose": "^0.23.6", "eslint": "^6.8.0", diff --git a/packages/standard-components/package.json b/packages/standard-components/package.json index 1a3677082..ccbdd00f3 100644 --- a/packages/standard-components/package.json +++ b/packages/standard-components/package.json @@ -29,11 +29,11 @@ "keywords": [ "svelte" ], - "version": "0.9.2", + "version": "0.9.3", "license": "MIT", "gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc", "dependencies": { - "@budibase/bbui": "^0.9.2", + "@budibase/bbui": "^0.9.3", "@spectrum-css/page": "^3.0.1", "@spectrum-css/vars": "^3.0.1", "apexcharts": "^3.22.1", diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 296ae1837..ae1e4f3a5 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/string-templates", - "version": "0.9.2", + "version": "0.9.3", "description": "Handlebars wrapper for Budibase templating.", "main": "src/index.cjs", "module": "dist/bundle.mjs", diff --git a/packages/worker/package.json b/packages/worker/package.json index 9ecb58ece..d849d9845 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/worker", "email": "hi@budibase.com", - "version": "0.9.2", + "version": "0.9.3", "description": "Budibase background service", "main": "src/index.js", "repository": { @@ -21,8 +21,8 @@ "author": "Budibase", "license": "AGPL-3.0-or-later", "dependencies": { - "@budibase/auth": "^0.9.2", - "@budibase/string-templates": "^0.9.2", + "@budibase/auth": "^0.9.3", + "@budibase/string-templates": "^0.9.3", "@koa/router": "^8.0.0", "aws-sdk": "^2.811.0", "bcryptjs": "^2.4.3", From db46750ce5a36f6a96e8531f11283d582aad81f4 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Mon, 24 May 2021 22:17:03 +0100 Subject: [PATCH 16/18] amend s3 path URL --- packages/server/src/constants/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/constants/index.js b/packages/server/src/constants/index.js index 728938759..b4af7b29e 100644 --- a/packages/server/src/constants/index.js +++ b/packages/server/src/constants/index.js @@ -103,7 +103,7 @@ exports.AutoFieldSubTypes = { AUTO_ID: "autoID", } -exports.OBJ_STORE_DIRECTORY = "/app-assets/assets" +exports.OBJ_STORE_DIRECTORY = "/prod-budi-app-assets/assets" exports.BaseQueryVerbs = { CREATE: "create", READ: "read", From 5c60629545a465584febcbb51bb8e90aef2fd2ed Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Mon, 24 May 2021 22:39:13 +0100 Subject: [PATCH 17/18] removing electron config --- packages/server/package.json | 26 ------------------- .../server/src/api/routes/tests/row.spec.js | 2 +- packages/server/src/constants/index.js | 2 +- 3 files changed, 2 insertions(+), 28 deletions(-) diff --git a/packages/server/package.json b/packages/server/package.json index e76c4e4e4..a8446bb76 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -8,29 +8,6 @@ "type": "git", "url": "https://github.com/Budibase/budibase.git" }, - "build": { - "icon": "./build/icons/512x512.png", - "appId": "com.budibase.builder", - "productName": "Budibase Builder", - "afterSign": "electron-builder-notarize", - "mac": { - "icon": "./assets/icons/icon.icns", - "category": "public.app-category.developer-tools", - "hardenedRuntime": true - }, - "linux": { - "maintainer": "Budibase", - "icon": "./build/icons/", - "target": [ - "deb", - "AppImage" - ], - "category": "Development" - }, - "extraMetadata": { - "name": "Budibase Builder" - } - }, "scripts": { "test": "jest --testPathIgnorePatterns=routes && yarn run test:integration", "test:integration": "jest --coverage --detectOpenHandles", @@ -41,9 +18,6 @@ "dev:stack:down": "node scripts/dev/manage.js down", "dev:stack:nuke": "node scripts/dev/manage.js nuke", "dev:builder": "yarn 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", "lint": "eslint --fix src/", "initialise": "node scripts/initialise.js" }, diff --git a/packages/server/src/api/routes/tests/row.spec.js b/packages/server/src/api/routes/tests/row.spec.js index f67d6254d..9d7413d6a 100644 --- a/packages/server/src/api/routes/tests/row.spec.js +++ b/packages/server/src/api/routes/tests/row.spec.js @@ -392,7 +392,7 @@ describe("/rows", () => { await setup.switchToSelfHosted(async () => { const enriched = await outputProcessing(config.getAppId(), table, [row]) expect(enriched[0].attachment[0].url).toBe( - `/prod-budi-app-assets/assets/${config.getAppId()}/attachment/test/thing.csv` + `/prod-budi-app-assets/${config.getAppId()}/attachment/test/thing.csv` ) }) }) diff --git a/packages/server/src/constants/index.js b/packages/server/src/constants/index.js index b4af7b29e..7311162db 100644 --- a/packages/server/src/constants/index.js +++ b/packages/server/src/constants/index.js @@ -103,7 +103,7 @@ exports.AutoFieldSubTypes = { AUTO_ID: "autoID", } -exports.OBJ_STORE_DIRECTORY = "/prod-budi-app-assets/assets" +exports.OBJ_STORE_DIRECTORY = "/prod-budi-app-assets" exports.BaseQueryVerbs = { CREATE: "create", READ: "read", From 38e6790146a4293fc10686b15e8d880990e41fd4 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Mon, 24 May 2021 22:50:58 +0100 Subject: [PATCH 18/18] fix test --- packages/server/src/api/routes/tests/row.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/api/routes/tests/row.spec.js b/packages/server/src/api/routes/tests/row.spec.js index 9d7413d6a..20ec4a20c 100644 --- a/packages/server/src/api/routes/tests/row.spec.js +++ b/packages/server/src/api/routes/tests/row.spec.js @@ -384,7 +384,7 @@ describe("/rows", () => { name: "test", description: "test", attachment: [{ - key: `/assets/${config.getAppId()}/attachment/test/thing.csv`, + key: `${config.getAppId()}/attachment/test/thing.csv`, }], tableId: table._id, })