diff --git a/packages/builder/src/builderStore/store/backend.js b/packages/builder/src/builderStore/store/backend.js index 1d606901ec..507b4e3fe9 100644 --- a/packages/builder/src/builderStore/store/backend.js +++ b/packages/builder/src/builderStore/store/backend.js @@ -8,6 +8,7 @@ const INITIAL_BACKEND_UI_STATE = { users: [], roles: [], datasources: [], + queries: [], selectedDatabase: {}, selectedTable: {}, draftTable: {}, @@ -24,10 +25,13 @@ export const getBackendUiStore = () => { const tables = await tablesResponse.json() const datasourcesResponse = await api.get(`/api/datasources`) const datasources = await datasourcesResponse.json() + const queriesResponse = await api.get(`/api/queries`) + const queries = await queriesResponse.json() store.update(state => { state.selectedDatabase = db state.tables = tables state.datasources = datasources + state.queries = queries return state }) }, @@ -98,35 +102,56 @@ export const getBackendUiStore = () => { return state }) }, - saveQuery: async (datasourceId, query) => { - const response = await api.post( - `/api/datasources/${datasourceId}/queries`, - query - ) + }, + queries: { + fetch: async () => { + const response = await api.get(`/api/queries`) const json = await response.json() store.update(state => { - const currentIdx = state.datasources.findIndex( - ds => ds._id === json._id + state.queries = json + return state + }) + return json + }, + save: async (datasourceId, query) => { + query.datasourceId = datasourceId + const response = await api.post(`/api/queries`, query) + const json = await response.json() + store.update(state => { + const currentIdx = state.queries.findIndex( + query => query._id === json._id ) if (currentIdx >= 0) { - state.datasources.splice(currentIdx, 1, json) + state.queries.splice(currentIdx, 1, json) } else { - state.datasources.push(json) + state.queries.push(json) } - state.datasources = state.datasources + state.queries = state.queries + state.selectedQueryId = json._id return state }) }, - }, - queries: { select: queryId => store.update(state => { state.selectedDatasourceId = null state.selectedQueryId = queryId return state }), + delete: async queryId => { + await api.delete(`/api/queries/${queryId}`) + store.update(state => { + state.datasources = state.queries.filter( + existing => existing._id !== queryId + ) + if (state.selectedQueryId === queryId) { + state.selectedQueryId = null + } + + return state + }) + }, }, tables: { fetch: async () => { diff --git a/packages/builder/src/components/backend/DataTable/ExternalDataSourceTable.svelte b/packages/builder/src/components/backend/DataTable/ExternalDataSourceTable.svelte index 85f23fe72b..a340f7c3c9 100644 --- a/packages/builder/src/components/backend/DataTable/ExternalDataSourceTable.svelte +++ b/packages/builder/src/components/backend/DataTable/ExternalDataSourceTable.svelte @@ -6,7 +6,6 @@ import Table from "./Table.svelte" import CreateQueryButton from "components/backend/DataTable/buttons/CreateQueryButton.svelte" - export let datasource export let query = {} let data = [] @@ -23,7 +22,6 @@ data = response.rows || [] error = false } catch (err) { - console.log(err) error = `${query}: Query error. (${err.message}). This could be a problem with your datasource configuration.` notifier.danger(error) } finally { @@ -32,14 +30,14 @@ } // Fetch rows for specified query - $: fetchData() + $: query && fetchData() {#if error}
{error}
{/if} - +
diff --git a/packages/builder/src/components/integration/index.svelte b/packages/builder/src/components/integration/index.svelte index 15021fe489..da301e3458 100644 --- a/packages/builder/src/components/integration/index.svelte +++ b/packages/builder/src/components/integration/index.svelte @@ -1,17 +1,28 @@ -{#if queryType === QueryTypes.SQL} + + +{#if type === QueryTypes.SQL} {/if} diff --git a/packages/builder/src/components/userInterface/EventsEditor/actions/ExecuteQuery.svelte b/packages/builder/src/components/userInterface/EventsEditor/actions/ExecuteQuery.svelte index 95b92835e6..8195e16258 100644 --- a/packages/builder/src/components/userInterface/EventsEditor/actions/ExecuteQuery.svelte +++ b/packages/builder/src/components/userInterface/EventsEditor/actions/ExecuteQuery.svelte @@ -24,7 +24,7 @@ diff --git a/packages/builder/src/components/userInterface/TableViewSelect.svelte b/packages/builder/src/components/userInterface/TableViewSelect.svelte index bcc5d05030..fed621b818 100644 --- a/packages/builder/src/components/userInterface/TableViewSelect.svelte +++ b/packages/builder/src/components/userInterface/TableViewSelect.svelte @@ -31,17 +31,13 @@ return [...acc, ...viewsArr] }, []) - $: queries = $backendUiStore.datasources.reduce((acc, cur) => { - let queriesArr = Object.entries(cur.queries).map(([key, value]) => ({ - label: value.name, - name: value.name, - datasourceId: cur._id, - queryId: key, - schema: value.schema, + $: queries = $backendUiStore.queries.map(query => ({ + label: query.name, + name: query.name, + ...query, + schema: query.schema, type: "query", - })) - return [...acc, ...queriesArr] - }, []) + })) $: bindableProperties = fetchBindableProperties({ componentInstanceId: $store.selectedComponentId, diff --git a/packages/builder/src/pages/[application]/data/datasource/[selectedDatasource]/[query]/index.svelte b/packages/builder/src/pages/[application]/data/datasource/[selectedDatasource]/[query]/index.svelte index 32c53a0c2d..27e3845584 100644 --- a/packages/builder/src/pages/[application]/data/datasource/[selectedDatasource]/[query]/index.svelte +++ b/packages/builder/src/pages/[application]/data/datasource/[selectedDatasource]/[query]/index.svelte @@ -3,13 +3,10 @@ import { backendUiStore } from "builderStore" import ExternalDataSourceTable from "components/backend/DataTable/ExternalDataSourceTable.svelte" - // TODO: refactor - $: datasource = $backendUiStore.datasources.find( - ds => ds._id === $params.selectedDatasource - ) - $: query = datasource && datasource.queries[$params.query] + $: query = $backendUiStore.queries.find(query => query._id === $params.query) + $: console.log("updated", query) -{#if $backendUiStore.selectedDatabase._id && datasource && query} - +{#if $backendUiStore.selectedDatabase._id && query} + {/if} diff --git a/packages/client/src/api/queries.js b/packages/client/src/api/queries.js index cfc92262a0..e3a4749f95 100644 --- a/packages/client/src/api/queries.js +++ b/packages/client/src/api/queries.js @@ -3,9 +3,9 @@ import API from "./api" /** * Fetches all rows from a query. */ -export const fetchQueryData = async ({ datasourceId, queryId }) => { +export const fetchQueryData = async ({ _id }) => { const response = await API.get({ - url: `/api/datasources/${datasourceId}/queries/${queryId}`, + url: `/api/queries/${_id}`, }) return response.rows } @@ -13,9 +13,9 @@ export const fetchQueryData = async ({ datasourceId, queryId }) => { /** * Executes a query against an external data connector. */ -export const executeQuery = async ({ datasourceId, queryId }) => { +export const executeQuery = async ({ _id }) => { const response = await API.post({ - url: `/api/datasources/${datasourceId}/queries/${queryId}`, + url: `/api/queries/${_id}`, // body: params, }) return response.rows diff --git a/packages/server/src/api/controllers/datasource.js b/packages/server/src/api/controllers/datasource.js index d2a963d89a..87e22d2e8e 100644 --- a/packages/server/src/api/controllers/datasource.js +++ b/packages/server/src/api/controllers/datasource.js @@ -1,11 +1,6 @@ const CouchDB = require("../../db") const bcrypt = require("../../utilities/bcrypt") -const { - generateDatasourceID, - getDatasourceParams, - generateQueryID, -} = require("../../db/utils") -const { integrations } = require("../../integrations") +const { generateDatasourceID, getDatasourceParams } = require("../../db/utils") exports.fetch = async function(ctx) { const database = new CouchDB(ctx.user.appId) @@ -30,7 +25,6 @@ exports.save = async function(ctx) { const datasource = { _id: generateDatasourceID(), type: "datasource", - queries: {}, ...ctx.request.body, } @@ -77,90 +71,3 @@ exports.find = async function(ctx) { const datasource = await database.get(ctx.params.datasourceId) ctx.body = datasource } - -exports.saveQuery = async function(ctx) { - const db = new CouchDB(ctx.user.appId) - const query = ctx.request.body - - // - // { - // type: "", - // query: "", - // otherStuff: "" - // } - - const datasource = await db.get(ctx.params.datasourceId) - - const queryId = generateQueryID() - - datasource.queries[queryId] = query - - const response = await db.put(datasource) - datasource._rev = response.rev - - ctx.body = datasource - ctx.message = `Query ${query.name} saved successfully.` -} - -exports.previewQuery = async function(ctx) { - const { type, config, query } = ctx.request.body - - const Integration = integrations[type] - - if (!Integration) { - ctx.throw(400, "Integration type does not exist.") - return - } - - ctx.body = await new Integration(config, query).query() -} - -exports.fetchQuery = async function(ctx) { - const db = new CouchDB(ctx.user.appId) - - const datasource = await db.get(ctx.params.datasourceId) - - const query = datasource.queries[ctx.params.queryId] - - const Integration = integrations[datasource.source] - - if (!Integration) { - ctx.throw(400, "Integration type does not exist.") - return - } - - const rows = await new Integration( - datasource.config, - query.queryString - ).query() - - ctx.body = { - schema: query.schema, - rows, - } -} - -exports.executeQuery = async function(ctx) { - const db = new CouchDB(ctx.user.appId) - - const datasource = await db.get(ctx.params.datasourceId) - - const query = datasource.queries[ctx.params.queryId] - - const Integration = integrations[datasource.source] - - if (!Integration) { - ctx.throw(400, "Integration type does not exist.") - return - } - - // TODO: allow the ability to POST parameters down when executing the query - // const customParams = ctx.request.body - - const response = await new Integration( - datasource.config, - query.queryString - ).query() - - ctx.body = response -} diff --git a/packages/server/src/api/controllers/query.js b/packages/server/src/api/controllers/query.js index 8cdd1f1567..229897debb 100644 --- a/packages/server/src/api/controllers/query.js +++ b/packages/server/src/api/controllers/query.js @@ -1,39 +1,128 @@ -// const CouchDB = require("../../../db") -// const { generateQueryID } = require("../../db/utils") -// const viewTemplate = require("./viewBuilder") +const handlebars = require("handlebars") +const Joi = require("joi") +const CouchDB = require("../../db") +const bcrypt = require("../../utilities/bcrypt") +const { generateQueryID, getQueryParams } = require("../../db/utils") +const { integrations } = require("../../integrations") +const joiValidator = require("../../middleware/joi-validator") -// exports.save = async ctx => { -// const db = new CouchDB(ctx.user.appId) -// const { datasourceId, query } = ctx.request.body +function generateQueryValidation() { + // prettier-ignore + return joiValidator.body(Joi.object({ + name: Joi.string().required(), + queryString: Joi.string().required(), + datasourceId: Joi.string().required(), + queryType: Joi.string().required(), + schema: Joi.object({}).required().unknown(true) + })) +} -// const datasource = await db.get(datasourceId) +exports.fetch = async function(ctx) { + const db = new CouchDB(ctx.user.appId) -// const queryId = generateQueryID() + const body = await db.allDocs( + getQueryParams(null, { + include_docs: true, + }) + ) + ctx.body = body.rows.map(row => row.doc) +} -// datasource.queries[queryId] = query +exports.save = async function(ctx) { + const db = new CouchDB(ctx.user.appId) + const query = ctx.request.body -// const response = await db.put(datasource) + // + // { + // type: "", + // query: "", + // otherStuff: "" + // } -// ctx.body = query -// ctx.message = `View ${viewToSave.name} saved successfully.` -// } + if (!query._id) { + query._id = generateQueryID(query.datasourceId) + } -// exports.destroy = async ctx => { -// const db = new CouchDB(ctx.user.appId) -// const designDoc = await db.get("_design/database") + const response = await db.put(query) + query._rev = response.rev -// const viewName = decodeURI(ctx.params.viewName) + ctx.body = query + ctx.message = `Query ${query.name} saved successfully.` +} -// const view = designDoc.views[viewName] +exports.preview = async function(ctx) { + const { query, datasourceId } = ctx.request.body -// delete designDoc.views[viewName] + const db = new CouchDB(ctx.user.appId) -// await db.put(designDoc) + const datasource = await db.get(datasourceId) -// const table = await db.get(view.meta.tableId) -// delete table.views[viewName] -// await db.put(table) + const Integration = integrations[datasource.source] -// ctx.body = view -// ctx.message = `View ${ctx.params.viewName} saved successfully.` -// } + if (!Integration) { + ctx.throw(400, "Integration type does not exist.") + return + } + + ctx.body = await new Integration(datasource.config, query).query() +} + +exports.execute = async function(ctx) { + const db = new CouchDB(ctx.user.appId) + + const datasource = await db.get(ctx.params.datasourceId) + + const query = datasource.queries[ctx.params.queryId] + + const Integration = integrations[datasource.source] + + if (!Integration) { + ctx.throw(400, "Integration type does not exist.") + return + } + + // TODO: allow the ability to POST parameters down when executing the query + // const customParams = ctx.request.body + const queryTemplate = handlebars.compile(query.queryString) + + const response = await new Integration( + datasource.config, + queryTemplate({ + // pass the params here from the UI and backend contexts + }) + ).query() + + ctx.body = response +} + +exports.fetchQuery = async function(ctx) { + const db = new CouchDB(ctx.user.appId) + + const query = await db.get(ctx.params.queryId) + + const datasource = await db.get(query.datasourceId) + + const Integration = integrations[datasource.source] + + if (!Integration) { + ctx.throw(400, "Integration type does not exist.") + return + } + + const rows = await new Integration( + datasource.config, + query.queryString + ).query() + + ctx.body = { + schema: query.schema, + rows, + } +} + +exports.destroy = async function(ctx) { + const db = new CouchDB(ctx.user.appId) + await db.destroy(ctx.params.queryId) + ctx.message = `Query deleted.` + ctx.status = 200 +} diff --git a/packages/server/src/api/routes/datasource.js b/packages/server/src/api/routes/datasource.js index 19930e6f48..2441d64399 100644 --- a/packages/server/src/api/routes/datasource.js +++ b/packages/server/src/api/routes/datasource.js @@ -17,26 +17,26 @@ router datasourceController.find ) .post("/api/datasources", authorized(BUILDER), datasourceController.save) - .post( - "/api/datasources/:datasourceId/queries", - authorized(BUILDER), - datasourceController.saveQuery - ) - .post( - "/api/datasources/queries/preview", - authorized(BUILDER), - datasourceController.previewQuery - ) - .get( - "/api/datasources/:datasourceId/queries/:queryId", - authorized(BUILDER), - datasourceController.fetchQuery - ) - .post( - "/api/datasources/:datasourceId/queries/:queryId", - authorized(BUILDER), - datasourceController.executeQuery - ) + // .post( + // "/api/datasources/:datasourceId/queries", + // authorized(BUILDER), + // datasourceController.saveQuery + // ) + // .post( + // "/api/datasources/queries/preview", + // authorized(BUILDER), + // datasourceController.previewQuery + // ) + // .get( + // "/api/datasources/:datasourceId/queries/:queryId", + // authorized(BUILDER), + // datasourceController.fetchQuery + // ) + // .post( + // "/api/datasources/:datasourceId/queries/:queryId", + // authorized(BUILDER), + // datasourceController.executeQuery + // ) .delete( "/api/datasources/:datasourceId/:revId", authorized(BUILDER), diff --git a/packages/server/src/api/routes/index.js b/packages/server/src/api/routes/index.js index 468c0a7b53..de5ce0eb0a 100644 --- a/packages/server/src/api/routes/index.js +++ b/packages/server/src/api/routes/index.js @@ -19,7 +19,7 @@ const routingRoutes = require("./routing") const integrationRoutes = require("./integration") const permissionRoutes = require("./permission") const datasourceRoutes = require("./datasource") -// const queryRoutes = require("./query") +const queryRoutes = require("./query") exports.mainRoutes = [ deployRoutes, @@ -39,7 +39,7 @@ exports.mainRoutes = [ integrationRoutes, permissionRoutes, datasourceRoutes, - // queryRoutes, + queryRoutes, // these need to be handled last as they still use /api/:tableId // this could be breaking as koa may recognise other routes as this tableRoutes, diff --git a/packages/server/src/api/routes/query.js b/packages/server/src/api/routes/query.js index b0da295703..06597e52cf 100644 --- a/packages/server/src/api/routes/query.js +++ b/packages/server/src/api/routes/query.js @@ -1,28 +1,17 @@ -// const Router = require("@koa/router") -// const queryController = require("../controllers/query") -// const authorized = require("../../middleware/authorized") -// const { BUILDER } = require("../../utilities/security/permissions") +const Router = require("@koa/router") +const queryController = require("../controllers/query") +const authorized = require("../../middleware/authorized") +const { BUILDER } = require("../../utilities/security/permissions") -// const router = Router() +const router = Router() -// // TODO: send down the datasource ID as well +// TODO: sort out auth so apps have the right permissions +router + .get("/api/queries", authorized(BUILDER), queryController.fetch) + .get("/api/queries/:queryId", authorized(BUILDER), queryController.fetchQuery) + .post("/api/queries", authorized(BUILDER), queryController.save) + .post("/api/queries/preview", authorized(BUILDER), queryController.preview) + .post("/api/queries/:queryId", authorized(BUILDER), queryController.execute) + .delete("/api/queries/:queryId", authorized(BUILDER), queryController.destroy) -// router -// // .get("/api/queries", authorized(BUILDER), queryController.fetch) -// // .get( -// // "/api/datasources/:datasourceId/queries/:id", -// // authorized(PermissionTypes.TABLE, PermissionLevels.READ), -// // queryController.find -// // ) -// .post( -// "/api/datasources/:datasourceId/queries", -// authorized(BUILDER), -// queryController.save -// ) -// .delete( -// "/api/datasources/:datasourceId/queries/:queryId/:revId", -// authorized(BUILDER), -// queryController.destroy -// ) - -// module.exports = router +module.exports = router diff --git a/packages/server/src/db/client.js b/packages/server/src/db/client.js index 1477ee4315..1d025a1402 100644 --- a/packages/server/src/db/client.js +++ b/packages/server/src/db/client.js @@ -38,6 +38,6 @@ function replicateLocal() { }) } -// replicateLocal() +replicateLocal() module.exports = Pouch diff --git a/packages/server/src/db/utils.js b/packages/server/src/db/utils.js index 946ce26dd2..4ebd95ca98 100644 --- a/packages/server/src/db/utils.js +++ b/packages/server/src/db/utils.js @@ -245,8 +245,10 @@ exports.getDatasourceParams = (datasourceId = null, otherProps = {}) => { * Generates a new query ID. * @returns {string} The new query ID which the query doc can be stored under. */ -exports.generateQueryID = () => { - return `${DocumentTypes.QUERY}${SEPARATOR}${newid()}` +exports.generateQueryID = datasourceId => { + return `${ + DocumentTypes.QUERY + }${SEPARATOR}${datasourceId}${SEPARATOR}${newid()}` } /** diff --git a/packages/server/src/utilities/security/permissions.js b/packages/server/src/utilities/security/permissions.js index 0836b29412..008145cc99 100644 --- a/packages/server/src/utilities/security/permissions.js +++ b/packages/server/src/utilities/security/permissions.js @@ -14,6 +14,7 @@ const PermissionTypes = { WEBHOOK: "webhook", BUILDER: "builder", VIEW: "view", + QUERY: "query", } function Permission(type, level) {