diff --git a/packages/builder/src/App.svelte b/packages/builder/src/App.svelte index 18742fa84c..de64d43249 100644 --- a/packages/builder/src/App.svelte +++ b/packages/builder/src/App.svelte @@ -17,14 +17,6 @@ } onMount(async () => { - const res = await fetch(`/api/client/id`) - const json = await res.json() - - store.update(state => { - state.clientId = json - return state - }) - window.addEventListener("error", showErrorBanner) window.addEventListener("unhandledrejection", showErrorBanner) }) @@ -34,8 +26,7 @@ -{#if $store.clientId} - - - -{/if} + + + + diff --git a/packages/builder/src/builderStore/loadComponentLibraries.js b/packages/builder/src/builderStore/loadComponentLibraries.js index 522d46fbe9..ada00134f4 100644 --- a/packages/builder/src/builderStore/loadComponentLibraries.js +++ b/packages/builder/src/builderStore/loadComponentLibraries.js @@ -1,11 +1,10 @@ /** * Fetches the definitions for component library components. This includes * their props and other metadata from components.json. - * @param {string} clientId - ID of the current client * @param {string} appId - ID of the currently running app */ -export const fetchComponentLibDefinitions = async (clientId, appId) => { - const LIB_DEFINITION_URL = `/${clientId}/${appId}/components/definitions` +export const fetchComponentLibDefinitions = async appId => { + const LIB_DEFINITION_URL = `/${appId}/components/definitions` try { const libDefinitionResponse = await fetch(LIB_DEFINITION_URL) return await libDefinitionResponse.json() diff --git a/packages/builder/src/builderStore/store/index.js b/packages/builder/src/builderStore/store/index.js index 7a7ee38b78..5adb1b88f3 100644 --- a/packages/builder/src/builderStore/store/index.js +++ b/packages/builder/src/builderStore/store/index.js @@ -94,10 +94,7 @@ const setPackage = (store, initial) => async pkg => { } initial.libraries = await fetchComponentLibModules(pkg.application) - initial.components = await fetchComponentLibDefinitions( - pkg.clientId, - pkg.application._id - ) + initial.components = await fetchComponentLibDefinitions(pkg.application._id) initial.appname = pkg.application.name initial.appId = pkg.application._id initial.pages = pkg.pages diff --git a/packages/builder/src/pages/[application]/_layout.svelte b/packages/builder/src/pages/[application]/_layout.svelte index 8149171878..df3b05a4c6 100644 --- a/packages/builder/src/pages/[application]/_layout.svelte +++ b/packages/builder/src/pages/[application]/_layout.svelte @@ -13,7 +13,7 @@ let promise = getPackage() async function getPackage() { - const res = await fetch(`/api/${$store.clientId}/${application}/appPackage`) + const res = await fetch(`/api/${application}/appPackage`) const pkg = await res.json() if (res.ok) { diff --git a/packages/cli/src/commands/init/initHandler.js b/packages/cli/src/commands/init/initHandler.js index 27b68e48c5..0467633ee8 100644 --- a/packages/cli/src/commands/init/initHandler.js +++ b/packages/cli/src/commands/init/initHandler.js @@ -13,6 +13,7 @@ module.exports = opts => { const run = async opts => { try { await ensureAppDir(opts) + await setEnvironmentVariables(opts) await prompts(opts) await createClientDatabase(opts) await createDevEnvFile(opts) @@ -22,18 +23,22 @@ const run = async opts => { } } -const ensureAppDir = async opts => { - opts.dir = xPlatHomeDir(opts.dir) - await ensureDir(opts.dir) - +const setEnvironmentVariables = async opts => { if (opts.database === "local") { const dataDir = join(opts.dir, ".data") await ensureDir(dataDir) process.env.COUCH_DB_URL = dataDir + (dataDir.endsWith("/") || dataDir.endsWith("\\") ? "" : "/") + } else { + process.env.COUCH_DB_URL = opts.couchDbUrl } } +const ensureAppDir = async opts => { + opts.dir = xPlatHomeDir(opts.dir) + await ensureDir(opts.dir) +} + const prompts = async opts => { const questions = [ { @@ -54,6 +59,10 @@ const prompts = async opts => { } const createClientDatabase = async opts => { + // cannot be a top level require as it + // will cause environment module to be loaded prematurely + const clientDb = require("@budibase/server/src/db/clientDb") + if (opts.clientId === "new") { // cannot be a top level require as it // will cause environment module to be loaded prematurely @@ -65,13 +74,10 @@ const createClientDatabase = async opts => { while (isExisting) { i += 1 opts.clientId = i.toString() - isExisting = existing.includes(`client-${opts.clientId}`) + isExisting = existing.includes(clientDb.name(opts.clientId)) } } - // cannot be a top level require as it - // will cause environment module to be loaded prematurely - const clientDb = require("@budibase/server/src/db/clientDb") await clientDb.create(opts.clientId) } diff --git a/packages/cli/src/commands/new/newHandler.js b/packages/cli/src/commands/new/newHandler.js index b533266d0e..e020c5f9da 100644 --- a/packages/cli/src/commands/new/newHandler.js +++ b/packages/cli/src/commands/new/newHandler.js @@ -19,7 +19,9 @@ const run = async opts => { exec(`cd ${join(opts.dir, opts.applicationId)} && npm install`) console.log(chalk.green(`Budibase app ${opts.name} created!`)) } catch (error) { - console.error(chalk.red("Error creating new app", error)) + console.error( + chalk.red("Error creating new app", JSON.stringify(error, { space: 2 })) + ) } } diff --git a/packages/server/.vscode/launch.json b/packages/server/.vscode/launch.json index 92bf424e35..964e9297f4 100644 --- a/packages/server/.vscode/launch.json +++ b/packages/server/.vscode/launch.json @@ -8,7 +8,7 @@ "type": "node", "request": "launch", "name": "Start Server", - "program": "${workspaceFolder}/src/index.js" + "program": "${workspaceFolder}/../cli/bin/budi" }, { "type": "node", @@ -88,6 +88,19 @@ "program": "${workspaceFolder}/node_modules/jest-cli/bin/jest", } }, + { + "type": "node", + "request": "launch", + "name": "Jest - Applications", + "program": "${workspaceFolder}/node_modules/.bin/jest", + "args": ["application.spec", "--runInBand"], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "disableOptimisticBPs": true, + "windows": { + "program": "${workspaceFolder}/node_modules/jest-cli/bin/jest", + } + }, { "type": "node", "request": "launch", diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index b50ab78f66..8160e4a5bf 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -1,11 +1,12 @@ const CouchDB = require("../../db") +const ClientDb = require("../../db/clientDb") const { getPackageForBuilder } = require("../../utilities/builder") const uuid = require("uuid") const env = require("../../environment") exports.fetch = async function(ctx) { - const clientDb = new CouchDB(`client-${env.CLIENT_ID}`) - const body = await clientDb.query("client/by_type", { + const db = new CouchDB(ClientDb.name(env.CLIENT_ID)) + const body = await db.query("client/by_type", { include_docs: true, key: ["app"], }) @@ -14,13 +15,13 @@ exports.fetch = async function(ctx) { } exports.fetchAppPackage = async function(ctx) { - const clientDb = new CouchDB(`client-${env.CLIENT_ID}`) - const application = await clientDb.get(ctx.params.applicationId) + const db = new CouchDB(ClientDb.name(env.CLIENT_ID)) + const application = await db.get(ctx.params.applicationId) ctx.body = await getPackageForBuilder(ctx.config, application) } exports.create = async function(ctx) { - const clientDb = new CouchDB(`client-${env.CLIENT_ID}`) + const db = new CouchDB(ClientDb.name(env.CLIENT_ID)) const newApplication = { _id: uuid.v4().replace(/-/g, ""), @@ -34,7 +35,7 @@ exports.create = async function(ctx) { ...ctx.request.body, } - const { rev } = await clientDb.post(newApplication) + const { rev } = await db.post(newApplication) newApplication._rev = rev ctx.body = newApplication ctx.message = `Application ${ctx.request.body.name} created successfully` diff --git a/packages/server/src/api/controllers/auth.js b/packages/server/src/api/controllers/auth.js index c55ccabe5b..4cfc2f438a 100644 --- a/packages/server/src/api/controllers/auth.js +++ b/packages/server/src/api/controllers/auth.js @@ -1,5 +1,6 @@ const jwt = require("jsonwebtoken") const CouchDB = require("../../db") +const ClientDb = require("../../db/clientDb") const bcrypt = require("../../utilities/bcrypt") const env = require("../../environment") @@ -14,7 +15,7 @@ exports.authenticate = async ctx => { const appId = referer[3] // find the instance that the user is associated with - const db = new CouchDB(`client-${env.CLIENT_ID}`) + const db = new CouchDB(ClientDb.name(env.CLIENT_ID)) const app = await db.get(appId) const instanceId = app.userInstanceMap[username] diff --git a/packages/server/src/api/controllers/component.js b/packages/server/src/api/controllers/component.js index 3523ed3f28..0b07826555 100644 --- a/packages/server/src/api/controllers/component.js +++ b/packages/server/src/api/controllers/component.js @@ -1,12 +1,14 @@ const CouchDB = require("../../db") +const ClientDb = require("../../db/clientDb") const { resolve, join } = require("path") const { budibaseTempDir, budibaseAppsDir, } = require("../../utilities/budibaseDir") +const env = require("../../environment") exports.fetchAppComponentDefinitions = async function(ctx) { - const db = new CouchDB(`client-${ctx.params.clientId}`) + const db = new CouchDB(ClientDb.name(env.CLIENT_ID)) const app = await db.get(ctx.params.appId) const componentDefinitions = app.componentLibraries.reduce( diff --git a/packages/server/src/api/controllers/instance.js b/packages/server/src/api/controllers/instance.js index a759777187..19edf53293 100644 --- a/packages/server/src/api/controllers/instance.js +++ b/packages/server/src/api/controllers/instance.js @@ -1,13 +1,12 @@ const CouchDB = require("../../db") +const client = require("../../db/clientDb") const uuid = require("uuid") const env = require("../../environment") -const clientDatabaseId = clientId => `client-${clientId}` - exports.create = async function(ctx) { const instanceName = ctx.request.body.name const uid = uuid.v4().replace(/-/g, "") - const instanceId = `${ctx.params.applicationId.substring(0,7)}_${uid}` + const instanceId = `inst_${ctx.params.applicationId.substring(0,7)}_${uid}` const { applicationId } = ctx.params const clientId = env.CLIENT_ID const db = new CouchDB(instanceId) @@ -34,7 +33,7 @@ exports.create = async function(ctx) { }) // Add the new instance under the app clientDB - const clientDb = new CouchDB(clientDatabaseId(clientId)) + const clientDb = new CouchDB(client.name(clientId)) const budibaseApp = await clientDb.get(applicationId) const instance = { _id: instanceId, name: instanceName } budibaseApp.instances.push(instance) @@ -52,7 +51,7 @@ exports.destroy = async function(ctx) { // remove instance from client application document const { metadata } = designDoc - const clientDb = new CouchDB(clientDatabaseId(metadata.clientId)) + const clientDb = new CouchDB(client.name(metadata.clientId)) const budibaseApp = await clientDb.get(metadata.applicationId) budibaseApp.instances = budibaseApp.instances.filter( instance => instance !== ctx.params.instanceId diff --git a/packages/server/src/api/controllers/static.js b/packages/server/src/api/controllers/static.js index 3badf1940a..aeaa4319f8 100644 --- a/packages/server/src/api/controllers/static.js +++ b/packages/server/src/api/controllers/static.js @@ -4,10 +4,11 @@ const { budibaseAppsDir, budibaseTempDir, } = require("../../utilities/budibaseDir") +const env = require("../../environment") exports.serveBuilder = async function(ctx) { let builderPath = resolve(process.cwd(), "builder") - + ctx.cookies.set("builder:token", env.ADMIN_SECRET) await send(ctx, ctx.file, { root: ctx.devPath || builderPath }) } diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js index a42e3dcf93..2be0132a47 100644 --- a/packages/server/src/api/controllers/user.js +++ b/packages/server/src/api/controllers/user.js @@ -1,4 +1,5 @@ const CouchDB = require("../../db") +const clientDb = require("../../db/clientDb") const bcrypt = require("../../utilities/bcrypt") const env = require("../../environment") @@ -30,14 +31,14 @@ exports.create = async function(ctx) { }) // the clientDB needs to store a map of users against the app - const clientDb = new CouchDB(`client-${env.CLIENT_ID}`) - const app = await clientDb.get(appId) + const db = new CouchDB(clientDb.name(env.CLIENT_ID)) + const app = await db.get(appId) app.userInstanceMap = { ...app.userInstanceMap, [username]: ctx.params.instanceId, } - await clientDb.put(app) + await db.put(app) ctx.status = 200 ctx.message = "User created successfully." diff --git a/packages/server/src/api/routes/component.js b/packages/server/src/api/routes/component.js index 3a5c7224c8..5df34381fa 100644 --- a/packages/server/src/api/routes/component.js +++ b/packages/server/src/api/routes/component.js @@ -4,7 +4,7 @@ const controller = require("../controllers/component") const router = Router() router.get( - "/:clientId/:appId/components/definitions", + "/:appId/components/definitions", controller.fetchAppComponentDefinitions ) diff --git a/packages/server/src/api/routes/static.js b/packages/server/src/api/routes/static.js index 606173b8fe..5909d31d09 100644 --- a/packages/server/src/api/routes/static.js +++ b/packages/server/src/api/routes/static.js @@ -1,21 +1,26 @@ const Router = require("@koa/router") const controller = require("../controllers/static") const { budibaseTempDir } = require("../../utilities/budibaseDir") +const env = require("../../environment") const router = Router() +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")) { + ctx.devPath = budibaseTempDir() + } + + await next() +}) + +if (env.NODE_ENV !== "production") { + router.get("/_builder/:file*", controller.serveBuilder) +} + 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")) { - ctx.devPath = budibaseTempDir() - } - - await next() - }) - .get("/_builder/:file*", controller.serveBuilder) .get("/:appId/componentlibrary", controller.serveComponentLibrary) .get("/:appId/:file*", controller.serveApp) diff --git a/packages/server/src/api/routes/tests/application.spec.js b/packages/server/src/api/routes/tests/application.spec.js index fa8c9d712e..3619b59477 100644 --- a/packages/server/src/api/routes/tests/application.spec.js +++ b/packages/server/src/api/routes/tests/application.spec.js @@ -1,6 +1,9 @@ const { createClientDatabase, - supertest + createApplication, + destroyClientDatabase, + supertest, + defaultHeaders } = require("./couchTestUtils") describe("/applications", () => { @@ -9,26 +12,47 @@ describe("/applications", () => { beforeAll(async () => { ({ request, server } = await supertest()) - await createClientDatabase(request) }); + beforeEach(async () => { + await createClientDatabase(request) + }) + + afterEach(async () => { + await destroyClientDatabase(request) + }) + afterAll(async () => { server.close() }) describe("create", () => { - it("returns a success message when the application is successfully created", done => { - request + it("returns a success message when the application is successfully created", async () => { + const res = await request .post("/api/applications") .send({ name: "My App" }) - .set("Accept", "application/json") + .set(defaultHeaders) .expect('Content-Type', /json/) .expect(200) - .end((err, res) => { - expect(res.res.statusMessage).toEqual("Application My App created successfully") - expect(res.body._id).toBeDefined() - done(); - }); - }) - }); -}); + expect(res.res.statusMessage).toEqual("Application My App created successfully") + expect(res.body._id).toBeDefined() + }) + }) + + describe("fetch", () => { + it("lists all applications", async () => { + + await createApplication(request, "app1") + await createApplication(request, "app2") + + const res = await request + .get("/api/applications") + .set(defaultHeaders) + .expect('Content-Type', /json/) + .expect(200) + + expect(res.body.length).toBe(2) + }) + }) + +}) diff --git a/packages/server/src/db/client.js b/packages/server/src/db/client.js index cb2867cd47..f2c090b564 100644 --- a/packages/server/src/db/client.js +++ b/packages/server/src/db/client.js @@ -3,7 +3,7 @@ const allDbs = require("pouchdb-all-dbs") const { budibaseAppsDir } = require("../utilities/budibaseDir") const env = require("../environment") -const COUCH_DB_URL = env.COUCH_DB_URL || `leveldb://${budibaseAppsDir()}/` +const COUCH_DB_URL = env.COUCH_DB_URL || `leveldb://${budibaseAppsDir().replace(/\\/g, "/")}/.data/` const isInMemory = env.NODE_ENV === "jest" if (isInMemory) PouchDB.plugin(require("pouchdb-adapter-memory")) diff --git a/packages/server/src/db/clientDb.js b/packages/server/src/db/clientDb.js index 1dace2c652..b85cd43110 100644 --- a/packages/server/src/db/clientDb.js +++ b/packages/server/src/db/clientDb.js @@ -1,7 +1,7 @@ const CouchDB = require("./client") exports.create = async clientId => { - const dbId = `client-${clientId}` + const dbId = exports.name(clientId) const db = new CouchDB(dbId) await db.put({ _id: "_design/client", @@ -17,6 +17,10 @@ exports.create = async clientId => { } exports.destroy = async function(clientId) { - const dbId = `client-${clientId}` + const dbId = exports.name(clientId) await new CouchDB(dbId).destroy() } + +exports.name = function(clientId) { + return `client_${clientId}` +} diff --git a/packages/server/src/middleware/authenticated.js b/packages/server/src/middleware/authenticated.js index 3dfeec4d35..43c8f013e1 100644 --- a/packages/server/src/middleware/authenticated.js +++ b/packages/server/src/middleware/authenticated.js @@ -15,6 +15,12 @@ module.exports = async (ctx, next) => { return } + if (ctx.isDev && ctx.cookies.get("builder:token") === env.ADMIN_SECRET) { + ctx.isAuthenticated = true + await next() + return + } + const token = ctx.cookies.get("budibase:token") if (!token) {