@budibase/server 0.9.188 → 0.9.190-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +7 -1
- package/__mocks__/mssql.ts +1 -1
- package/__mocks__/oracledb.ts +31 -0
- package/__mocks__/pg.ts +3 -0
- package/builder/assets/bb-space-man.5be51a45.svg +290 -0
- package/builder/assets/index.7d405e74.css +1 -0
- package/builder/assets/index.edea4fa5.js +1316 -0
- package/builder/assets/oracle.66592424.png +0 -0
- package/builder/assets/vendor.470042a4.js +152 -0
- package/builder/assets/vendor.5e1fe644.css +1 -0
- package/builder/index.html +4 -4
- package/coverage/clover.xml +1507 -1081
- package/coverage/coverage-final.json +93 -89
- package/coverage/lcov-report/api/controllers/analytics.js.html +2 -2
- package/coverage/lcov-report/api/controllers/apikeys.js.html +2 -2
- package/coverage/lcov-report/api/controllers/application.js.html +64 -13
- package/coverage/lcov-report/api/controllers/auth.js.html +19 -10
- package/coverage/lcov-report/api/controllers/automation.js.html +3 -3
- package/coverage/lcov-report/api/controllers/backup.js.html +1 -1
- package/coverage/lcov-report/api/controllers/cloud.js.html +1 -1
- package/coverage/lcov-report/api/controllers/component.js.html +4 -4
- package/coverage/lcov-report/api/controllers/datasource.js.html +232 -28
- package/coverage/lcov-report/api/controllers/dev.js.html +16 -7
- package/coverage/lcov-report/api/controllers/hosting.js.html +10 -10
- package/coverage/lcov-report/api/controllers/index.html +91 -91
- package/coverage/lcov-report/api/controllers/integration.js.html +1 -1
- package/coverage/lcov-report/api/controllers/layout.js.html +3 -3
- package/coverage/lcov-report/api/controllers/metadata.js.html +3 -3
- package/coverage/lcov-report/api/controllers/permission.js.html +3 -3
- package/coverage/lcov-report/api/controllers/query.js.html +53 -209
- package/coverage/lcov-report/api/controllers/role.js.html +9 -9
- package/coverage/lcov-report/api/controllers/routing.js.html +3 -3
- package/coverage/lcov-report/api/controllers/row/external.js.html +17 -8
- package/coverage/lcov-report/api/controllers/row/index.html +26 -26
- package/coverage/lcov-report/api/controllers/row/index.js.html +2 -2
- package/coverage/lcov-report/api/controllers/row/internal.js.html +9 -9
- package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +2 -2
- package/coverage/lcov-report/api/controllers/row/utils.js.html +21 -30
- package/coverage/lcov-report/api/controllers/screen.js.html +2 -2
- package/coverage/lcov-report/api/controllers/script.js.html +3 -3
- package/coverage/lcov-report/api/controllers/table/external.js.html +1001 -0
- package/coverage/lcov-report/api/controllers/table/index.html +56 -26
- package/coverage/lcov-report/api/controllers/table/index.js.html +80 -350
- package/coverage/lcov-report/api/controllers/table/internal.js.html +545 -0
- package/coverage/lcov-report/api/controllers/table/utils.js.html +175 -52
- package/coverage/lcov-report/api/controllers/templates.js.html +4 -4
- package/coverage/lcov-report/api/controllers/user.js.html +390 -33
- package/coverage/lcov-report/api/controllers/view/exporters.js.html +19 -4
- package/coverage/lcov-report/api/controllers/view/index.html +22 -22
- package/coverage/lcov-report/api/controllers/view/index.js.html +52 -13
- package/coverage/lcov-report/api/controllers/view/utils.js.html +11 -11
- package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +3 -3
- package/coverage/lcov-report/api/controllers/webhook.js.html +4 -4
- package/coverage/lcov-report/api/index.html +1 -1
- package/coverage/lcov-report/api/index.js.html +1 -1
- package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
- package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
- package/coverage/lcov-report/api/routes/application.js.html +1 -1
- package/coverage/lcov-report/api/routes/auth.js.html +1 -1
- package/coverage/lcov-report/api/routes/automation.js.html +1 -1
- package/coverage/lcov-report/api/routes/backup.js.html +1 -1
- package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
- package/coverage/lcov-report/api/routes/component.js.html +1 -1
- package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
- package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
- package/coverage/lcov-report/api/routes/dev.js.html +2 -2
- package/coverage/lcov-report/api/routes/hosting.js.html +1 -1
- package/coverage/lcov-report/api/routes/index.html +4 -4
- package/coverage/lcov-report/api/routes/index.js.html +1 -1
- package/coverage/lcov-report/api/routes/integration.js.html +1 -1
- package/coverage/lcov-report/api/routes/layout.js.html +1 -1
- package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
- package/coverage/lcov-report/api/routes/permission.js.html +1 -1
- package/coverage/lcov-report/api/routes/query.js.html +5 -2
- package/coverage/lcov-report/api/routes/role.js.html +1 -1
- package/coverage/lcov-report/api/routes/routing.js.html +1 -1
- package/coverage/lcov-report/api/routes/row.js.html +1 -1
- package/coverage/lcov-report/api/routes/screen.js.html +1 -1
- package/coverage/lcov-report/api/routes/script.js.html +1 -1
- package/coverage/lcov-report/api/routes/static.js.html +2 -2
- package/coverage/lcov-report/api/routes/table.js.html +35 -2
- package/coverage/lcov-report/api/routes/templates.js.html +1 -1
- package/coverage/lcov-report/api/routes/user.js.html +17 -2
- package/coverage/lcov-report/api/routes/view.js.html +1 -1
- package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
- package/coverage/lcov-report/automations/actions.js.html +2 -2
- package/coverage/lcov-report/automations/automationUtils.js.html +4 -4
- package/coverage/lcov-report/automations/bullboard.js.html +71 -20
- package/coverage/lcov-report/automations/index.html +35 -50
- package/coverage/lcov-report/automations/index.js.html +2 -2
- package/coverage/lcov-report/automations/steps/bash.js.html +1 -1
- package/coverage/lcov-report/automations/steps/createRow.js.html +1 -1
- package/coverage/lcov-report/automations/steps/delay.js.html +1 -1
- package/coverage/lcov-report/automations/steps/deleteRow.js.html +1 -1
- package/coverage/lcov-report/automations/steps/discord.js.html +3 -3
- package/coverage/lcov-report/automations/steps/executeQuery.js.html +1 -1
- package/coverage/lcov-report/automations/steps/executeScript.js.html +3 -3
- package/coverage/lcov-report/automations/steps/filter.js.html +1 -1
- package/coverage/lcov-report/automations/steps/index.html +14 -14
- package/coverage/lcov-report/automations/steps/integromat.js.html +1 -1
- package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +3 -3
- package/coverage/lcov-report/automations/steps/queryRows.js.html +3 -3
- package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +1 -1
- package/coverage/lcov-report/automations/steps/serverLog.js.html +1 -1
- package/coverage/lcov-report/automations/steps/slack.js.html +1 -1
- package/coverage/lcov-report/automations/steps/updateRow.js.html +1 -1
- package/coverage/lcov-report/automations/steps/utils.js.html +7 -7
- package/coverage/lcov-report/automations/steps/zapier.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/app.js.html +2 -2
- package/coverage/lcov-report/automations/triggerInfo/cron.js.html +2 -2
- package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/index.js.html +8 -8
- package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +2 -2
- package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +2 -2
- package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +2 -2
- package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +2 -2
- package/coverage/lcov-report/automations/triggers.js.html +22 -10
- package/coverage/lcov-report/automations/utils.js.html +42 -12
- package/coverage/lcov-report/constants/index.html +5 -5
- package/coverage/lcov-report/constants/index.js.html +87 -21
- package/coverage/lcov-report/constants/layouts.js.html +1 -1
- package/coverage/lcov-report/constants/screens.js.html +1 -1
- package/coverage/lcov-report/db/client.js.html +2 -2
- package/coverage/lcov-report/db/inMemoryView.js.html +2 -2
- package/coverage/lcov-report/db/index.html +2 -2
- package/coverage/lcov-report/db/index.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/LinkController.js.html +6 -6
- package/coverage/lcov-report/db/linkedRows/index.html +10 -10
- package/coverage/lcov-report/db/linkedRows/index.js.html +5 -5
- package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +6 -6
- package/coverage/lcov-report/db/newid.js.html +3 -3
- package/coverage/lcov-report/db/utils.js.html +57 -57
- package/coverage/lcov-report/events/AutomationEmitter.js.html +6 -6
- package/coverage/lcov-report/events/index.html +6 -6
- package/coverage/lcov-report/events/index.js.html +1 -1
- package/coverage/lcov-report/events/utils.js.html +5 -5
- package/coverage/lcov-report/index.html +106 -91
- package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
- package/coverage/lcov-report/integrations/tests/index.html +1 -1
- package/coverage/lcov-report/middleware/appInfo.js.html +2 -2
- package/coverage/lcov-report/middleware/authorized.js.html +11 -8
- package/coverage/lcov-report/middleware/builder.js.html +16 -7
- package/coverage/lcov-report/middleware/currentapp.js.html +6 -6
- package/coverage/lcov-report/middleware/index.html +23 -23
- package/coverage/lcov-report/middleware/joi-validator.js.html +2 -2
- package/coverage/lcov-report/middleware/resourceId.js.html +18 -18
- package/coverage/lcov-report/middleware/selfhost.js.html +2 -2
- package/coverage/lcov-report/middleware/usageQuota.js.html +4 -4
- package/coverage/lcov-report/middleware/utils.js.html +1 -1
- package/coverage/lcov-report/{automations/thread.js.html → threads/automation.js.html} +90 -33
- package/coverage/lcov-report/threads/index.html +141 -0
- package/coverage/lcov-report/threads/index.js.html +278 -0
- package/coverage/lcov-report/threads/query.js.html +278 -0
- package/coverage/lcov-report/utilities/bcrypt.js.html +1 -1
- package/coverage/lcov-report/utilities/budibaseDir.js.html +1 -1
- package/coverage/lcov-report/utilities/centralPath.js.html +1 -1
- package/coverage/lcov-report/utilities/csvParser.js.html +144 -15
- package/coverage/lcov-report/utilities/global.js.html +26 -11
- package/coverage/lcov-report/utilities/index.html +48 -48
- package/coverage/lcov-report/utilities/index.js.html +47 -14
- package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +30 -9
- package/coverage/lcov-report/utilities/queue/index.html +14 -14
- package/coverage/lcov-report/utilities/routing/index.html +1 -1
- package/coverage/lcov-report/utilities/routing/index.js.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/index.html +4 -4
- package/coverage/lcov-report/utilities/rowProcessor/index.js.html +2 -2
- package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +3 -3
- package/coverage/lcov-report/utilities/scriptRunner.js.html +16 -7
- package/coverage/lcov-report/utilities/security.js.html +3 -3
- package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
- package/coverage/lcov-report/utilities/users.js.html +1 -1
- package/coverage/lcov-report/utilities/workerRequests.js.html +5 -5
- package/coverage/lcov.info +2495 -1737
- package/dist/api/controllers/application.js +15 -1
- package/dist/api/controllers/auth.js +3 -3
- package/dist/api/controllers/datasource.js +67 -16
- package/dist/api/controllers/deploy/index.js +2 -0
- package/dist/api/controllers/dev.js +4 -1
- package/dist/api/controllers/query.js +30 -58
- package/dist/api/controllers/row/ExternalRequest.js +78 -34
- package/dist/api/controllers/row/external.js +3 -1
- package/dist/api/controllers/row/utils.js +6 -12
- package/dist/api/controllers/table/external.js +249 -0
- package/dist/api/controllers/table/index.js +49 -123
- package/dist/api/controllers/table/internal.js +150 -0
- package/dist/api/controllers/table/utils.js +62 -28
- package/dist/api/controllers/user.js +123 -11
- package/dist/api/controllers/view/exporters.js +4 -0
- package/dist/api/controllers/view/index.js +14 -2
- package/dist/api/routes/query.js +1 -1
- package/dist/api/routes/table.js +7 -1
- package/dist/api/routes/user.js +2 -1
- package/dist/app.js +2 -11
- package/dist/automations/bullboard.js +37 -10
- package/dist/automations/triggers.js +7 -3
- package/dist/automations/utils.js +11 -2
- package/dist/constants/index.js +19 -0
- package/dist/definitions/datasource.js +5 -0
- package/dist/environment.js +13 -0
- package/dist/integrations/base/IntegrationBase.js +2 -0
- package/dist/integrations/base/datasourcePlus.js +2 -0
- package/dist/integrations/base/sql.js +353 -177
- package/dist/integrations/base/sqlTable.js +143 -0
- package/dist/integrations/base/utils.js +30 -0
- package/dist/integrations/index.js +6 -0
- package/dist/integrations/microsoftSqlServer.js +117 -9
- package/dist/integrations/mysql.js +16 -85
- package/dist/integrations/oracle.js +347 -0
- package/dist/integrations/postgres.js +35 -28
- package/dist/integrations/utils.js +71 -9
- package/dist/middleware/authorized.js +2 -1
- package/dist/middleware/builder.js +4 -1
- package/dist/package.json +12 -7
- package/dist/tests/utilities/TestConfiguration.js +2 -1
- package/dist/{automations/thread.js → threads/automation.js} +26 -8
- package/dist/threads/index.js +64 -0
- package/dist/threads/query.js +64 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utilities/csvParser.js +46 -5
- package/dist/utilities/global.js +6 -2
- package/dist/utilities/index.js +11 -0
- package/dist/utilities/queue/inMemoryQueue.js +8 -0
- package/dist/utilities/scriptRunner.js +4 -1
- package/package.json +13 -8
- package/scripts/dev/manage.js +1 -0
- package/scripts/integrations/mssql/data/Dockerfile +9 -0
- package/scripts/integrations/mssql/data/entrypoint.sh +24 -0
- package/scripts/integrations/mssql/data/setup.sql +54 -0
- package/scripts/integrations/mssql/docker-compose.yaml +12 -0
- package/scripts/integrations/mssql/reset.sh +3 -0
- package/scripts/integrations/oracle/docker-compose.yml +19 -0
- package/scripts/integrations/oracle/instantclient/linux/x86-64/basiclite-21.4.zip +0 -0
- package/scripts/integrations/oracle/instantclient/linux/x86-64/install.sh +23 -0
- package/scripts/integrations/oracle/oracle.md +90 -0
- package/scripts/integrations/postgres/docker-compose.yml +3 -3
- package/scripts/likeCypress.ts +33 -0
- package/src/api/controllers/application.js +18 -1
- package/src/api/controllers/auth.js +3 -0
- package/src/api/controllers/datasource.js +84 -16
- package/src/api/controllers/deploy/index.js +2 -0
- package/src/api/controllers/dev.js +4 -1
- package/src/api/controllers/query.js +29 -81
- package/src/api/controllers/row/ExternalRequest.ts +91 -36
- package/src/api/controllers/row/external.js +4 -1
- package/src/api/controllers/row/utils.js +8 -11
- package/src/api/controllers/table/external.js +307 -0
- package/src/api/controllers/table/index.js +47 -137
- package/src/api/controllers/table/internal.js +155 -0
- package/src/api/controllers/table/utils.js +74 -33
- package/src/api/controllers/user.js +132 -13
- package/src/api/controllers/view/exporters.js +5 -0
- package/src/api/controllers/view/index.js +15 -2
- package/src/api/routes/query.js +1 -0
- package/src/api/routes/table.js +11 -0
- package/src/api/routes/tests/datasource.spec.js +2 -2
- package/src/api/routes/tests/misc.spec.js +5 -1
- package/src/api/routes/tests/query.spec.js +0 -1
- package/src/api/routes/user.js +5 -0
- package/src/app.ts +2 -10
- package/src/automations/bullboard.js +28 -11
- package/src/automations/tests/automation.spec.js +3 -4
- package/src/automations/triggers.js +7 -3
- package/src/automations/utils.js +12 -2
- package/src/constants/index.js +22 -0
- package/src/definitions/common.ts +7 -1
- package/src/definitions/datasource.ts +10 -3
- package/src/environment.js +15 -0
- package/src/integrations/airtable.ts +2 -1
- package/src/integrations/arangodb.ts +2 -1
- package/src/integrations/base/IntegrationBase.ts +6 -0
- package/src/integrations/base/datasourcePlus.ts +9 -0
- package/src/integrations/base/sql.ts +377 -218
- package/src/integrations/base/sqlTable.ts +180 -0
- package/src/integrations/base/utils.ts +22 -0
- package/src/integrations/couchdb.ts +2 -1
- package/src/integrations/dynamodb.ts +2 -1
- package/src/integrations/elasticsearch.ts +2 -1
- package/src/integrations/index.ts +7 -0
- package/src/integrations/microsoftSqlServer.ts +145 -13
- package/src/integrations/mongodb.ts +2 -1
- package/src/integrations/mysql.ts +23 -95
- package/src/integrations/oracle.ts +458 -0
- package/src/integrations/postgres.ts +45 -35
- package/src/integrations/rest.ts +2 -1
- package/src/integrations/s3.ts +2 -1
- package/src/integrations/tests/microsoftSqlServer.spec.js +22 -15
- package/src/integrations/tests/oracle.spec.js +94 -0
- package/src/integrations/tests/postgres.spec.js +4 -0
- package/src/integrations/utils.ts +78 -8
- package/src/middleware/authorized.js +2 -1
- package/src/middleware/builder.js +4 -1
- package/src/tests/utilities/TestConfiguration.js +2 -1
- package/src/{automations/thread.js → threads/automation.js} +27 -8
- package/src/threads/index.js +66 -0
- package/src/threads/query.js +66 -0
- package/src/utilities/csvParser.js +48 -5
- package/src/utilities/global.js +7 -2
- package/src/utilities/index.js +11 -0
- package/src/utilities/queue/inMemoryQueue.js +7 -0
- package/src/utilities/scriptRunner.js +4 -1
- package/src/utilities/tests/__snapshots__/csvParser.spec.js.snap +0 -6
- package/src/utilities/tests/csvParser.spec.js +3 -0
- package/tsconfig.json +1 -1
- package/builder/assets/index.550fdba7.js +0 -201
- package/builder/assets/index.7469e1e4.css +0 -29
- package/builder/assets/vendor.278a305c.js +0 -9
- package/builder/assets/vendor.d75d5eb7.css +0 -1
|
@@ -4,8 +4,8 @@ const CouchDB = require("../../../db")
|
|
|
4
4
|
const { InternalTables } = require("../../../db/utils")
|
|
5
5
|
const userController = require("../user")
|
|
6
6
|
const { FieldTypes } = require("../../../constants")
|
|
7
|
-
const { integrations } = require("../../../integrations")
|
|
8
7
|
const { processStringSync } = require("@budibase/string-templates")
|
|
8
|
+
const { makeExternalQuery } = require("../../../integrations/base/utils")
|
|
9
9
|
|
|
10
10
|
validateJs.extend(validateJs.validators.datetime, {
|
|
11
11
|
parse: function (value) {
|
|
@@ -17,18 +17,11 @@ validateJs.extend(validateJs.validators.datetime, {
|
|
|
17
17
|
},
|
|
18
18
|
})
|
|
19
19
|
|
|
20
|
-
exports.
|
|
20
|
+
exports.getDatasourceAndQuery = async (appId, json) => {
|
|
21
21
|
const datasourceId = json.endpoint.datasourceId
|
|
22
22
|
const db = new CouchDB(appId)
|
|
23
23
|
const datasource = await db.get(datasourceId)
|
|
24
|
-
|
|
25
|
-
// query is the opinionated function
|
|
26
|
-
if (Integration.prototype.query) {
|
|
27
|
-
const integration = new Integration(datasource.config)
|
|
28
|
-
return integration.query(json)
|
|
29
|
-
} else {
|
|
30
|
-
throw "Datasource does not support query."
|
|
31
|
-
}
|
|
24
|
+
return makeExternalQuery(datasource, json)
|
|
32
25
|
}
|
|
33
26
|
|
|
34
27
|
exports.findRow = async (ctx, db, tableId, rowId) => {
|
|
@@ -68,7 +61,11 @@ exports.validate = async ({ appId, tableId, row, table }) => {
|
|
|
68
61
|
let res
|
|
69
62
|
|
|
70
63
|
// Validate.js doesn't seem to handle array
|
|
71
|
-
if (
|
|
64
|
+
if (
|
|
65
|
+
table.schema[fieldName].type === FieldTypes.ARRAY &&
|
|
66
|
+
row[fieldName] &&
|
|
67
|
+
row[fieldName].length
|
|
68
|
+
) {
|
|
72
69
|
row[fieldName].map(val => {
|
|
73
70
|
if (!constraints.inclusion.includes(val)) {
|
|
74
71
|
errors[fieldName] = "Field not in list"
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
const CouchDB = require("../../../db")
|
|
2
|
+
const {
|
|
3
|
+
buildExternalTableId,
|
|
4
|
+
breakExternalTableId,
|
|
5
|
+
} = require("../../../integrations/utils")
|
|
6
|
+
const {
|
|
7
|
+
getTable,
|
|
8
|
+
generateForeignKey,
|
|
9
|
+
generateJunctionTableName,
|
|
10
|
+
foreignKeyStructure,
|
|
11
|
+
hasTypeChanged,
|
|
12
|
+
} = require("./utils")
|
|
13
|
+
const {
|
|
14
|
+
DataSourceOperation,
|
|
15
|
+
FieldTypes,
|
|
16
|
+
RelationshipTypes,
|
|
17
|
+
} = require("../../../constants")
|
|
18
|
+
const { makeExternalQuery } = require("../../../integrations/base/utils")
|
|
19
|
+
const { cloneDeep } = require("lodash/fp")
|
|
20
|
+
const csvParser = require("../../../utilities/csvParser")
|
|
21
|
+
const { handleRequest } = require("../row/external")
|
|
22
|
+
|
|
23
|
+
async function makeTableRequest(
|
|
24
|
+
datasource,
|
|
25
|
+
operation,
|
|
26
|
+
table,
|
|
27
|
+
tables,
|
|
28
|
+
oldTable = null
|
|
29
|
+
) {
|
|
30
|
+
const json = {
|
|
31
|
+
endpoint: {
|
|
32
|
+
datasourceId: datasource._id,
|
|
33
|
+
entityId: table._id,
|
|
34
|
+
operation,
|
|
35
|
+
},
|
|
36
|
+
meta: {
|
|
37
|
+
tables,
|
|
38
|
+
},
|
|
39
|
+
table,
|
|
40
|
+
}
|
|
41
|
+
if (oldTable) {
|
|
42
|
+
json.meta.table = oldTable
|
|
43
|
+
}
|
|
44
|
+
return makeExternalQuery(datasource, json)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function cleanupRelationships(table, tables, oldTable = null) {
|
|
48
|
+
const tableToIterate = oldTable ? oldTable : table
|
|
49
|
+
// clean up relationships in couch table schemas
|
|
50
|
+
for (let [key, schema] of Object.entries(tableToIterate.schema)) {
|
|
51
|
+
if (
|
|
52
|
+
schema.type === FieldTypes.LINK &&
|
|
53
|
+
(!oldTable || table.schema[key] == null)
|
|
54
|
+
) {
|
|
55
|
+
const relatedTable = Object.values(tables).find(
|
|
56
|
+
table => table._id === schema.tableId
|
|
57
|
+
)
|
|
58
|
+
const foreignKey = schema.foreignKey
|
|
59
|
+
if (!relatedTable || !foreignKey) {
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
for (let [relatedKey, relatedSchema] of Object.entries(
|
|
63
|
+
relatedTable.schema
|
|
64
|
+
)) {
|
|
65
|
+
if (
|
|
66
|
+
relatedSchema.type === FieldTypes.LINK &&
|
|
67
|
+
relatedSchema.fieldName === foreignKey
|
|
68
|
+
) {
|
|
69
|
+
delete relatedTable.schema[relatedKey]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getDatasourceId(table) {
|
|
77
|
+
if (!table) {
|
|
78
|
+
throw "No table supplied"
|
|
79
|
+
}
|
|
80
|
+
if (table.sourceId) {
|
|
81
|
+
return table.sourceId
|
|
82
|
+
}
|
|
83
|
+
return breakExternalTableId(table._id).datasourceId
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function otherRelationshipType(type) {
|
|
87
|
+
if (type === RelationshipTypes.MANY_TO_MANY) {
|
|
88
|
+
return RelationshipTypes.MANY_TO_MANY
|
|
89
|
+
}
|
|
90
|
+
return type === RelationshipTypes.ONE_TO_MANY
|
|
91
|
+
? RelationshipTypes.MANY_TO_ONE
|
|
92
|
+
: RelationshipTypes.ONE_TO_MANY
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function generateManyLinkSchema(datasource, column, table, relatedTable) {
|
|
96
|
+
const primary = table.name + table.primary[0]
|
|
97
|
+
const relatedPrimary = relatedTable.name + relatedTable.primary[0]
|
|
98
|
+
const jcTblName = generateJunctionTableName(column, table, relatedTable)
|
|
99
|
+
// first create the new table
|
|
100
|
+
const junctionTable = {
|
|
101
|
+
_id: buildExternalTableId(datasource._id, jcTblName),
|
|
102
|
+
name: jcTblName,
|
|
103
|
+
primary: [primary, relatedPrimary],
|
|
104
|
+
constrained: [primary, relatedPrimary],
|
|
105
|
+
schema: {
|
|
106
|
+
[primary]: foreignKeyStructure(primary, {
|
|
107
|
+
toTable: table.name,
|
|
108
|
+
toKey: table.primary[0],
|
|
109
|
+
}),
|
|
110
|
+
[relatedPrimary]: foreignKeyStructure(relatedPrimary, {
|
|
111
|
+
toTable: relatedTable.name,
|
|
112
|
+
toKey: relatedTable.primary[0],
|
|
113
|
+
}),
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
column.through = junctionTable._id
|
|
117
|
+
column.throughFrom = primary
|
|
118
|
+
column.throughTo = relatedPrimary
|
|
119
|
+
column.fieldName = relatedPrimary
|
|
120
|
+
return junctionTable
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function generateLinkSchema(column, table, relatedTable, type) {
|
|
124
|
+
const isOneSide = type === RelationshipTypes.ONE_TO_MANY
|
|
125
|
+
const primary = isOneSide ? relatedTable.primary[0] : table.primary[0]
|
|
126
|
+
// generate a foreign key
|
|
127
|
+
const foreignKey = generateForeignKey(column, relatedTable)
|
|
128
|
+
column.relationshipType = type
|
|
129
|
+
column.foreignKey = isOneSide ? foreignKey : primary
|
|
130
|
+
column.fieldName = isOneSide ? primary : foreignKey
|
|
131
|
+
return foreignKey
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function generateRelatedSchema(linkColumn, table, relatedTable, columnName) {
|
|
135
|
+
// generate column for other table
|
|
136
|
+
const relatedSchema = cloneDeep(linkColumn)
|
|
137
|
+
// swap them from the main link
|
|
138
|
+
if (linkColumn.foreignKey) {
|
|
139
|
+
relatedSchema.fieldName = linkColumn.foreignKey
|
|
140
|
+
relatedSchema.foreignKey = linkColumn.fieldName
|
|
141
|
+
}
|
|
142
|
+
// is many to many
|
|
143
|
+
else {
|
|
144
|
+
// don't need to copy through, already got it
|
|
145
|
+
relatedSchema.fieldName = linkColumn.throughFrom
|
|
146
|
+
relatedSchema.throughTo = linkColumn.throughFrom
|
|
147
|
+
relatedSchema.throughFrom = linkColumn.throughTo
|
|
148
|
+
}
|
|
149
|
+
relatedSchema.relationshipType = otherRelationshipType(
|
|
150
|
+
linkColumn.relationshipType
|
|
151
|
+
)
|
|
152
|
+
relatedSchema.tableId = relatedTable._id
|
|
153
|
+
relatedSchema.name = columnName
|
|
154
|
+
table.schema[columnName] = relatedSchema
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function isRelationshipSetup(column) {
|
|
158
|
+
return column.foreignKey || column.through
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
exports.save = async function (ctx) {
|
|
162
|
+
const appId = ctx.appId
|
|
163
|
+
const table = ctx.request.body
|
|
164
|
+
// can't do this right now
|
|
165
|
+
delete table.dataImport
|
|
166
|
+
const datasourceId = getDatasourceId(ctx.request.body)
|
|
167
|
+
// table doesn't exist already, note that it is created
|
|
168
|
+
if (!table._id) {
|
|
169
|
+
table.created = true
|
|
170
|
+
}
|
|
171
|
+
let tableToSave = {
|
|
172
|
+
type: "table",
|
|
173
|
+
_id: buildExternalTableId(datasourceId, table.name),
|
|
174
|
+
...table,
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let oldTable
|
|
178
|
+
if (ctx.request.body && ctx.request.body._id) {
|
|
179
|
+
oldTable = await getTable(appId, ctx.request.body._id)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (hasTypeChanged(tableToSave, oldTable)) {
|
|
183
|
+
ctx.throw(400, "A column type has changed.")
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const db = new CouchDB(appId)
|
|
187
|
+
const datasource = await db.get(datasourceId)
|
|
188
|
+
const oldTables = cloneDeep(datasource.entities)
|
|
189
|
+
const tables = datasource.entities
|
|
190
|
+
|
|
191
|
+
const extraTablesToUpdate = []
|
|
192
|
+
|
|
193
|
+
// check if relations need setup
|
|
194
|
+
for (let schema of Object.values(tableToSave.schema)) {
|
|
195
|
+
if (schema.type !== FieldTypes.LINK || isRelationshipSetup(schema)) {
|
|
196
|
+
continue
|
|
197
|
+
}
|
|
198
|
+
const relatedTable = Object.values(tables).find(
|
|
199
|
+
table => table._id === schema.tableId
|
|
200
|
+
)
|
|
201
|
+
const relatedColumnName = schema.fieldName
|
|
202
|
+
const relationType = schema.relationshipType
|
|
203
|
+
if (relationType === RelationshipTypes.MANY_TO_MANY) {
|
|
204
|
+
const junctionTable = generateManyLinkSchema(
|
|
205
|
+
datasource,
|
|
206
|
+
schema,
|
|
207
|
+
table,
|
|
208
|
+
relatedTable
|
|
209
|
+
)
|
|
210
|
+
if (tables[junctionTable.name]) {
|
|
211
|
+
throw "Junction table already exists, cannot create another relationship."
|
|
212
|
+
}
|
|
213
|
+
tables[junctionTable.name] = junctionTable
|
|
214
|
+
extraTablesToUpdate.push(junctionTable)
|
|
215
|
+
} else {
|
|
216
|
+
const fkTable =
|
|
217
|
+
relationType === RelationshipTypes.ONE_TO_MANY ? table : relatedTable
|
|
218
|
+
const foreignKey = generateLinkSchema(
|
|
219
|
+
schema,
|
|
220
|
+
table,
|
|
221
|
+
relatedTable,
|
|
222
|
+
relationType
|
|
223
|
+
)
|
|
224
|
+
fkTable.schema[foreignKey] = foreignKeyStructure(foreignKey)
|
|
225
|
+
if (fkTable.constrained == null) {
|
|
226
|
+
fkTable.constrained = []
|
|
227
|
+
}
|
|
228
|
+
if (fkTable.constrained.indexOf(foreignKey) === -1) {
|
|
229
|
+
fkTable.constrained.push(foreignKey)
|
|
230
|
+
}
|
|
231
|
+
// foreign key is in other table, need to save it to external
|
|
232
|
+
if (fkTable._id !== table._id) {
|
|
233
|
+
extraTablesToUpdate.push(fkTable)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
generateRelatedSchema(schema, relatedTable, table, relatedColumnName)
|
|
237
|
+
schema.main = true
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
cleanupRelationships(tableToSave, tables, oldTable)
|
|
241
|
+
|
|
242
|
+
const operation = oldTable
|
|
243
|
+
? DataSourceOperation.UPDATE_TABLE
|
|
244
|
+
: DataSourceOperation.CREATE_TABLE
|
|
245
|
+
await makeTableRequest(datasource, operation, tableToSave, tables, oldTable)
|
|
246
|
+
// update any extra tables (like foreign keys in other tables)
|
|
247
|
+
for (let extraTable of extraTablesToUpdate) {
|
|
248
|
+
const oldExtraTable = oldTables[extraTable.name]
|
|
249
|
+
let op = oldExtraTable
|
|
250
|
+
? DataSourceOperation.UPDATE_TABLE
|
|
251
|
+
: DataSourceOperation.CREATE_TABLE
|
|
252
|
+
await makeTableRequest(datasource, op, extraTable, tables, oldExtraTable)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// make sure the constrained list, all still exist
|
|
256
|
+
if (Array.isArray(tableToSave.constrained)) {
|
|
257
|
+
tableToSave.constrained = tableToSave.constrained.filter(constraint =>
|
|
258
|
+
Object.keys(tableToSave.schema).includes(constraint)
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// store it into couch now for budibase reference
|
|
263
|
+
datasource.entities[tableToSave.name] = tableToSave
|
|
264
|
+
await db.put(datasource)
|
|
265
|
+
|
|
266
|
+
return tableToSave
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
exports.destroy = async function (ctx) {
|
|
270
|
+
const appId = ctx.appId
|
|
271
|
+
const tableToDelete = await getTable(appId, ctx.params.tableId)
|
|
272
|
+
if (!tableToDelete || !tableToDelete.created) {
|
|
273
|
+
ctx.throw(400, "Cannot delete tables which weren't created in Budibase.")
|
|
274
|
+
}
|
|
275
|
+
const datasourceId = getDatasourceId(tableToDelete)
|
|
276
|
+
|
|
277
|
+
const db = new CouchDB(appId)
|
|
278
|
+
const datasource = await db.get(datasourceId)
|
|
279
|
+
const tables = datasource.entities
|
|
280
|
+
|
|
281
|
+
const operation = DataSourceOperation.DELETE_TABLE
|
|
282
|
+
await makeTableRequest(datasource, operation, tableToDelete, tables)
|
|
283
|
+
|
|
284
|
+
cleanupRelationships(tableToDelete, tables)
|
|
285
|
+
|
|
286
|
+
delete datasource.entities[tableToDelete.name]
|
|
287
|
+
await db.put(datasource)
|
|
288
|
+
|
|
289
|
+
return tableToDelete
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
exports.bulkImport = async function (ctx) {
|
|
293
|
+
const appId = ctx.appId
|
|
294
|
+
const table = await getTable(appId, ctx.params.tableId)
|
|
295
|
+
const { dataImport } = ctx.request.body
|
|
296
|
+
if (!dataImport || !dataImport.schema || !dataImport.csvString) {
|
|
297
|
+
ctx.throw(400, "Provided data import information is invalid.")
|
|
298
|
+
}
|
|
299
|
+
const rows = await csvParser.transform({
|
|
300
|
+
...dataImport,
|
|
301
|
+
existingTable: table,
|
|
302
|
+
})
|
|
303
|
+
await handleRequest(appId, DataSourceOperation.BULK_CREATE, table._id, {
|
|
304
|
+
rows,
|
|
305
|
+
})
|
|
306
|
+
return table
|
|
307
|
+
}
|
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
const CouchDB = require("../../../db")
|
|
2
|
-
const
|
|
2
|
+
const internal = require("./internal")
|
|
3
|
+
const external = require("./external")
|
|
3
4
|
const csvParser = require("../../../utilities/csvParser")
|
|
5
|
+
const { isExternalTable } = require("../../../integrations/utils")
|
|
4
6
|
const {
|
|
5
|
-
getRowParams,
|
|
6
7
|
getTableParams,
|
|
7
|
-
generateTableID,
|
|
8
8
|
getDatasourceParams,
|
|
9
9
|
BudibaseInternalDB,
|
|
10
10
|
} = require("../../../db/utils")
|
|
11
|
-
const {
|
|
12
|
-
const { TableSaveFunctions, getTable } = require("./utils")
|
|
11
|
+
const { getTable } = require("./utils")
|
|
13
12
|
|
|
13
|
+
function pickApi({ tableId, table }) {
|
|
14
|
+
if (table && !tableId) {
|
|
15
|
+
tableId = table._id
|
|
16
|
+
}
|
|
17
|
+
if (table && table.type === "external") {
|
|
18
|
+
return external
|
|
19
|
+
} else if (tableId && isExternalTable(tableId)) {
|
|
20
|
+
return external
|
|
21
|
+
}
|
|
22
|
+
return internal
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// covers both internal and external
|
|
14
26
|
exports.fetch = async function (ctx) {
|
|
15
27
|
const db = new CouchDB(ctx.appId)
|
|
16
28
|
|
|
@@ -50,147 +62,45 @@ exports.find = async function (ctx) {
|
|
|
50
62
|
|
|
51
63
|
exports.save = async function (ctx) {
|
|
52
64
|
const appId = ctx.appId
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
let tableToSave = {
|
|
56
|
-
type: "table",
|
|
57
|
-
_id: generateTableID(),
|
|
58
|
-
views: {},
|
|
59
|
-
...rest,
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// if the table obj had an _id then it will have been retrieved
|
|
63
|
-
let oldTable
|
|
64
|
-
if (ctx.request.body && ctx.request.body._id) {
|
|
65
|
-
oldTable = await db.get(ctx.request.body._id)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// saving a table is a complex operation, involving many different steps, this
|
|
69
|
-
// has been broken out into a utility to make it more obvious/easier to manipulate
|
|
70
|
-
const tableSaveFunctions = new TableSaveFunctions({
|
|
71
|
-
db,
|
|
72
|
-
ctx,
|
|
73
|
-
oldTable,
|
|
74
|
-
dataImport,
|
|
75
|
-
})
|
|
76
|
-
tableToSave = await tableSaveFunctions.before(tableToSave)
|
|
77
|
-
|
|
78
|
-
// make sure that types don't change of a column, have to remove
|
|
79
|
-
// the column if you want to change the type
|
|
80
|
-
if (oldTable && oldTable.schema) {
|
|
81
|
-
for (let propKey of Object.keys(tableToSave.schema)) {
|
|
82
|
-
let column = tableToSave.schema[propKey]
|
|
83
|
-
let oldColumn = oldTable.schema[propKey]
|
|
84
|
-
if (oldColumn && oldColumn.type === "internal") {
|
|
85
|
-
oldColumn.type = "auto"
|
|
86
|
-
}
|
|
87
|
-
if (oldColumn && oldColumn.type !== column.type) {
|
|
88
|
-
ctx.throw(400, "Cannot change the type of a column")
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Don't rename if the name is the same
|
|
94
|
-
let { _rename } = tableToSave
|
|
95
|
-
/* istanbul ignore next */
|
|
96
|
-
if (_rename && _rename.old === _rename.updated) {
|
|
97
|
-
_rename = null
|
|
98
|
-
delete tableToSave._rename
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// rename row fields when table column is renamed
|
|
102
|
-
/* istanbul ignore next */
|
|
103
|
-
if (_rename && tableToSave.schema[_rename.updated].type === FieldTypes.LINK) {
|
|
104
|
-
ctx.throw(400, "Cannot rename a linked column.")
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
tableToSave = await tableSaveFunctions.mid(tableToSave)
|
|
108
|
-
|
|
109
|
-
// update schema of non-statistics views when new columns are added
|
|
110
|
-
for (let view in tableToSave.views) {
|
|
111
|
-
const tableView = tableToSave.views[view]
|
|
112
|
-
if (!tableView) continue
|
|
113
|
-
|
|
114
|
-
if (tableView.schema.group || tableView.schema.field) continue
|
|
115
|
-
tableView.schema = tableToSave.schema
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// update linked rows
|
|
119
|
-
try {
|
|
120
|
-
const linkResp = await linkRows.updateLinks({
|
|
121
|
-
appId,
|
|
122
|
-
eventType: oldTable
|
|
123
|
-
? linkRows.EventType.TABLE_UPDATED
|
|
124
|
-
: linkRows.EventType.TABLE_SAVE,
|
|
125
|
-
table: tableToSave,
|
|
126
|
-
oldTable: oldTable,
|
|
127
|
-
})
|
|
128
|
-
if (linkResp != null && linkResp._rev) {
|
|
129
|
-
tableToSave._rev = linkResp._rev
|
|
130
|
-
}
|
|
131
|
-
} catch (err) {
|
|
132
|
-
ctx.throw(400, err)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// don't perform any updates until relationships have been
|
|
136
|
-
// checked by the updateLinks function
|
|
137
|
-
const updatedRows = tableSaveFunctions.getUpdatedRows()
|
|
138
|
-
if (updatedRows && updatedRows.length !== 0) {
|
|
139
|
-
await db.bulkDocs(updatedRows)
|
|
140
|
-
}
|
|
141
|
-
const result = await db.put(tableToSave)
|
|
142
|
-
tableToSave._rev = result.rev
|
|
143
|
-
|
|
144
|
-
tableToSave = await tableSaveFunctions.after(tableToSave)
|
|
145
|
-
|
|
146
|
-
ctx.eventEmitter &&
|
|
147
|
-
ctx.eventEmitter.emitTable(`table:save`, appId, tableToSave)
|
|
148
|
-
|
|
65
|
+
const table = ctx.request.body
|
|
66
|
+
const savedTable = await pickApi({ table }).save(ctx)
|
|
149
67
|
ctx.status = 200
|
|
150
|
-
ctx.message = `Table ${
|
|
151
|
-
ctx.
|
|
68
|
+
ctx.message = `Table ${table.name} saved successfully.`
|
|
69
|
+
ctx.eventEmitter &&
|
|
70
|
+
ctx.eventEmitter.emitTable(`table:save`, appId, savedTable)
|
|
71
|
+
ctx.body = savedTable
|
|
152
72
|
}
|
|
153
73
|
|
|
154
74
|
exports.destroy = async function (ctx) {
|
|
155
75
|
const appId = ctx.appId
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
// Delete all rows for that table
|
|
160
|
-
const rows = await db.allDocs(
|
|
161
|
-
getRowParams(ctx.params.tableId, null, {
|
|
162
|
-
include_docs: true,
|
|
163
|
-
})
|
|
164
|
-
)
|
|
165
|
-
await db.bulkDocs(rows.rows.map(row => ({ ...row.doc, _deleted: true })))
|
|
166
|
-
|
|
167
|
-
// update linked rows
|
|
168
|
-
await linkRows.updateLinks({
|
|
169
|
-
appId,
|
|
170
|
-
eventType: linkRows.EventType.TABLE_DELETE,
|
|
171
|
-
table: tableToDelete,
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
// don't remove the table itself until very end
|
|
175
|
-
await db.remove(tableToDelete)
|
|
176
|
-
|
|
177
|
-
// remove table search index
|
|
178
|
-
const currentIndexes = await db.getIndexes()
|
|
179
|
-
const existingIndex = currentIndexes.indexes.find(
|
|
180
|
-
existing => existing.name === `search:${ctx.params.tableId}`
|
|
181
|
-
)
|
|
182
|
-
if (existingIndex) {
|
|
183
|
-
await db.deleteIndex(existingIndex)
|
|
184
|
-
}
|
|
185
|
-
|
|
76
|
+
const tableId = ctx.params.tableId
|
|
77
|
+
const deletedTable = await pickApi({ tableId }).destroy(ctx)
|
|
186
78
|
ctx.eventEmitter &&
|
|
187
|
-
ctx.eventEmitter.emitTable(`table:delete`, appId,
|
|
79
|
+
ctx.eventEmitter.emitTable(`table:delete`, appId, deletedTable)
|
|
188
80
|
ctx.status = 200
|
|
189
|
-
ctx.body = { message: `Table ${
|
|
81
|
+
ctx.body = { message: `Table ${tableId} deleted.` }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
exports.bulkImport = async function (ctx) {
|
|
85
|
+
const tableId = ctx.params.tableId
|
|
86
|
+
await pickApi({ tableId }).bulkImport(ctx)
|
|
87
|
+
// right now we don't trigger anything for bulk import because it
|
|
88
|
+
// can only be done in the builder, but in the future we may need to
|
|
89
|
+
// think about events for bulk items
|
|
90
|
+
ctx.status = 200
|
|
91
|
+
ctx.body = { message: `Bulk rows created.` }
|
|
190
92
|
}
|
|
191
93
|
|
|
192
94
|
exports.validateCSVSchema = async function (ctx) {
|
|
193
|
-
|
|
194
|
-
const
|
|
95
|
+
// tableId being specified means its an import to an existing table
|
|
96
|
+
const { csvString, schema = {}, tableId } = ctx.request.body
|
|
97
|
+
let existingTable
|
|
98
|
+
if (tableId) {
|
|
99
|
+
existingTable = await getTable(ctx.appId, tableId)
|
|
100
|
+
}
|
|
101
|
+
let result = await csvParser.parse(csvString, schema)
|
|
102
|
+
if (existingTable) {
|
|
103
|
+
result = csvParser.updateSchema({ schema: result, existingTable })
|
|
104
|
+
}
|
|
195
105
|
ctx.body = { schema: result }
|
|
196
106
|
}
|