@budibase/server 1.0.46-alpha.4 → 1.0.46-alpha.8
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/builder/assets/{index.c21ee1fb.js → index.51efc215.js} +243 -243
- package/builder/assets/{index.243ca52c.css → index.dd9e386f.css} +1 -1
- package/builder/assets/{vendor.10e2ad1b.js → vendor.3bd2f661.js} +1 -1
- package/builder/index.html +3 -3
- package/coverage/clover.xml +1794 -1793
- package/coverage/coverage-final.json +136 -138
- package/coverage/lcov-report/api/controllers/analytics.js.html +6 -6
- package/coverage/lcov-report/api/controllers/apikeys.js.html +6 -6
- package/coverage/lcov-report/api/controllers/application.js.html +209 -125
- package/coverage/lcov-report/api/controllers/auth.js.html +23 -11
- package/coverage/lcov-report/api/controllers/automation.js.html +21 -21
- package/coverage/lcov-report/api/controllers/backup.js.html +3 -3
- package/coverage/lcov-report/api/controllers/cloud.js.html +12 -12
- package/coverage/lcov-report/api/controllers/component.js.html +5 -5
- package/coverage/lcov-report/api/controllers/datasource.js.html +58 -22
- package/coverage/lcov-report/api/controllers/dev.js.html +19 -19
- package/coverage/lcov-report/api/controllers/index.html +38 -53
- package/coverage/lcov-report/api/controllers/integration.js.html +4 -4
- package/coverage/lcov-report/api/controllers/layout.js.html +6 -6
- package/coverage/lcov-report/api/controllers/metadata.js.html +9 -9
- package/coverage/lcov-report/api/controllers/permission.js.html +15 -15
- package/coverage/lcov-report/api/controllers/query/index.html +15 -15
- package/coverage/lcov-report/api/controllers/query/index.js.html +61 -28
- package/coverage/lcov-report/api/controllers/query/validation.js.html +9 -9
- package/coverage/lcov-report/api/controllers/role.js.html +9 -9
- package/coverage/lcov-report/api/controllers/routing.js.html +9 -9
- package/coverage/lcov-report/api/controllers/row/external.js.html +16 -16
- package/coverage/lcov-report/api/controllers/row/index.html +5 -5
- package/coverage/lcov-report/api/controllers/row/index.js.html +13 -13
- package/coverage/lcov-report/api/controllers/row/internal.js.html +26 -26
- package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +8 -8
- package/coverage/lcov-report/api/controllers/row/utils.js.html +17 -26
- package/coverage/lcov-report/api/controllers/screen.js.html +7 -7
- package/coverage/lcov-report/api/controllers/script.js.html +4 -4
- package/coverage/lcov-report/api/controllers/table/external.js.html +12 -12
- package/coverage/lcov-report/api/controllers/table/index.html +1 -1
- package/coverage/lcov-report/api/controllers/table/index.js.html +14 -14
- package/coverage/lcov-report/api/controllers/table/internal.js.html +10 -10
- package/coverage/lcov-report/api/controllers/table/utils.js.html +26 -26
- package/coverage/lcov-report/api/controllers/templates.js.html +5 -5
- package/coverage/lcov-report/api/controllers/user.js.html +30 -24
- package/coverage/lcov-report/api/controllers/view/exporters.js.html +4 -4
- package/coverage/lcov-report/api/controllers/view/index.html +1 -1
- package/coverage/lcov-report/api/controllers/view/index.js.html +14 -14
- package/coverage/lcov-report/api/controllers/view/utils.js.html +11 -11
- package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +7 -7
- package/coverage/lcov-report/api/controllers/webhook.js.html +15 -15
- package/coverage/lcov-report/api/index.html +1 -1
- package/coverage/lcov-report/api/index.js.html +26 -26
- package/coverage/lcov-report/api/routes/analytics.js.html +6 -6
- package/coverage/lcov-report/api/routes/apikeys.js.html +8 -8
- package/coverage/lcov-report/api/routes/application.js.html +9 -9
- package/coverage/lcov-report/api/routes/auth.js.html +6 -6
- package/coverage/lcov-report/api/routes/automation.js.html +14 -14
- package/coverage/lcov-report/api/routes/backup.js.html +8 -8
- package/coverage/lcov-report/api/routes/cloud.js.html +8 -8
- package/coverage/lcov-report/api/routes/component.js.html +8 -8
- package/coverage/lcov-report/api/routes/datasource.js.html +13 -13
- package/coverage/lcov-report/api/routes/deploy.js.html +8 -8
- package/coverage/lcov-report/api/routes/dev.js.html +13 -13
- package/coverage/lcov-report/api/routes/index.html +7 -22
- package/coverage/lcov-report/api/routes/index.js.html +31 -37
- package/coverage/lcov-report/api/routes/integration.js.html +8 -8
- package/coverage/lcov-report/api/routes/layout.js.html +8 -8
- package/coverage/lcov-report/api/routes/metadata.js.html +9 -9
- package/coverage/lcov-report/api/routes/permission.js.html +12 -12
- package/coverage/lcov-report/api/routes/query.js.html +10 -10
- package/coverage/lcov-report/api/routes/role.js.html +12 -12
- package/coverage/lcov-report/api/routes/routing.js.html +8 -8
- package/coverage/lcov-report/api/routes/row.js.html +10 -10
- package/coverage/lcov-report/api/routes/screen.js.html +11 -11
- package/coverage/lcov-report/api/routes/script.js.html +1 -1
- package/coverage/lcov-report/api/routes/static.js.html +13 -13
- package/coverage/lcov-report/api/routes/table.js.html +12 -12
- package/coverage/lcov-report/api/routes/templates.js.html +8 -8
- package/coverage/lcov-report/api/routes/user.js.html +8 -8
- package/coverage/lcov-report/api/routes/view.js.html +10 -10
- package/coverage/lcov-report/api/routes/webhook.js.html +11 -11
- package/coverage/lcov-report/automations/actions.js.html +24 -24
- package/coverage/lcov-report/automations/automationUtils.js.html +5 -5
- package/coverage/lcov-report/automations/bullboard.js.html +16 -16
- package/coverage/lcov-report/automations/index.html +1 -1
- package/coverage/lcov-report/automations/index.js.html +7 -7
- package/coverage/lcov-report/automations/steps/bash.js.html +6 -6
- package/coverage/lcov-report/automations/steps/createRow.js.html +7 -7
- package/coverage/lcov-report/automations/steps/delay.js.html +4 -4
- package/coverage/lcov-report/automations/steps/deleteRow.js.html +7 -7
- package/coverage/lcov-report/automations/steps/discord.js.html +7 -7
- package/coverage/lcov-report/automations/steps/executeQuery.js.html +6 -6
- package/coverage/lcov-report/automations/steps/executeScript.js.html +6 -6
- package/coverage/lcov-report/automations/steps/filter.js.html +7 -7
- package/coverage/lcov-report/automations/steps/index.html +1 -1
- package/coverage/lcov-report/automations/steps/integromat.js.html +5 -5
- package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +8 -8
- package/coverage/lcov-report/automations/steps/queryRows.js.html +10 -10
- package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +5 -5
- package/coverage/lcov-report/automations/steps/serverLog.js.html +3 -3
- package/coverage/lcov-report/automations/steps/slack.js.html +5 -5
- package/coverage/lcov-report/automations/steps/updateRow.js.html +6 -6
- package/coverage/lcov-report/automations/steps/utils.js.html +3 -3
- package/coverage/lcov-report/automations/steps/zapier.js.html +5 -5
- 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 +18 -18
- package/coverage/lcov-report/automations/utils.js.html +32 -32
- package/coverage/lcov-report/constants/index.html +5 -5
- package/coverage/lcov-report/constants/index.js.html +36 -24
- package/coverage/lcov-report/constants/layouts.js.html +5 -5
- package/coverage/lcov-report/constants/screens.js.html +4 -4
- package/coverage/lcov-report/db/client.js.html +18 -18
- package/coverage/lcov-report/db/inMemoryView.js.html +7 -7
- package/coverage/lcov-report/db/index.html +1 -1
- package/coverage/lcov-report/db/index.js.html +3 -3
- package/coverage/lcov-report/db/linkedRows/LinkController.js.html +7 -7
- package/coverage/lcov-report/db/linkedRows/index.html +1 -1
- package/coverage/lcov-report/db/linkedRows/index.js.html +18 -18
- package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +13 -13
- package/coverage/lcov-report/db/newid.js.html +4 -4
- package/coverage/lcov-report/db/utils.js.html +73 -73
- package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
- package/coverage/lcov-report/events/index.html +1 -1
- package/coverage/lcov-report/events/index.js.html +6 -6
- package/coverage/lcov-report/events/utils.js.html +3 -3
- package/coverage/lcov-report/index.html +62 -62
- 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 +5 -5
- package/coverage/lcov-report/middleware/authorized.js.html +171 -69
- package/coverage/lcov-report/middleware/builder.js.html +16 -16
- package/coverage/lcov-report/middleware/currentapp.js.html +46 -73
- package/coverage/lcov-report/middleware/index.html +24 -24
- package/coverage/lcov-report/middleware/joi-validator.js.html +6 -6
- package/coverage/lcov-report/middleware/resourceId.js.html +22 -22
- package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
- package/coverage/lcov-report/middleware/usageQuota.js.html +17 -17
- package/coverage/lcov-report/middleware/utils.js.html +4 -4
- package/coverage/lcov-report/migrations/usageQuotas/index.html +1 -1
- package/coverage/lcov-report/migrations/usageQuotas/index.js.html +6 -6
- package/coverage/lcov-report/migrations/usageQuotas/syncApps.js.html +6 -6
- package/coverage/lcov-report/migrations/usageQuotas/syncRows.js.html +7 -7
- package/coverage/lcov-report/threads/automation.js.html +1 -1
- package/coverage/lcov-report/threads/index.html +15 -15
- package/coverage/lcov-report/threads/index.js.html +10 -10
- package/coverage/lcov-report/threads/query.js.html +23 -8
- package/coverage/lcov-report/threads/utils.js.html +14 -14
- package/coverage/lcov-report/utilities/bcrypt.js.html +1 -1
- package/coverage/lcov-report/utilities/budibaseDir.js.html +7 -7
- package/coverage/lcov-report/utilities/centralPath.js.html +4 -4
- package/coverage/lcov-report/utilities/csvParser.js.html +6 -6
- package/coverage/lcov-report/utilities/global.js.html +26 -26
- package/coverage/lcov-report/utilities/index.html +19 -19
- package/coverage/lcov-report/utilities/index.js.html +25 -25
- package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +9 -9
- package/coverage/lcov-report/utilities/queue/index.html +1 -1
- package/coverage/lcov-report/utilities/routing/index.html +1 -1
- package/coverage/lcov-report/utilities/routing/index.js.html +6 -6
- package/coverage/lcov-report/utilities/rowProcessor/index.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/index.js.html +17 -17
- package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +4 -4
- package/coverage/lcov-report/utilities/scriptRunner.js.html +5 -5
- package/coverage/lcov-report/utilities/security.js.html +8 -8
- package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
- package/coverage/lcov-report/utilities/usageQuota/index.html +1 -1
- package/coverage/lcov-report/utilities/usageQuota/index.js.html +8 -8
- package/coverage/lcov-report/utilities/usageQuota/rows.js.html +8 -8
- package/coverage/lcov-report/utilities/usageQuota/usageQuoteReset.js.html +1 -1
- package/coverage/lcov-report/utilities/users.js.html +5 -5
- package/coverage/lcov-report/utilities/workerRequests.js.html +39 -108
- package/coverage/lcov.info +2292 -2272
- package/dist/api/controllers/application.js +49 -20
- package/dist/api/controllers/auth.js +4 -0
- package/dist/api/controllers/datasource.js +11 -0
- package/dist/api/controllers/query/index.js +11 -3
- package/dist/api/controllers/row/utils.js +1 -2
- package/dist/api/controllers/static/index.js +9 -10
- package/dist/api/controllers/user.js +2 -0
- package/dist/api/routes/index.js +0 -2
- package/dist/constants/index.js +3 -0
- package/dist/definitions/datasource.js +1 -0
- package/dist/integrations/googlesheets.js +340 -0
- package/dist/integrations/index.js +3 -0
- package/dist/integrations/utils.js +6 -3
- package/dist/middleware/authorized.js +55 -30
- package/dist/middleware/currentapp.js +0 -9
- package/dist/package.json +8 -5
- package/dist/tests/utilities/TestConfiguration.js +10 -6
- package/dist/threads/query.js +4 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utilities/workerRequests.js +0 -20
- package/nodemon.json +1 -1
- package/package.json +9 -6
- package/src/api/controllers/application.js +52 -24
- package/src/api/controllers/auth.js +4 -0
- package/src/api/controllers/datasource.js +12 -0
- package/src/api/controllers/query/index.js +14 -3
- package/src/api/controllers/row/utils.js +1 -4
- package/src/api/controllers/static/index.js +13 -10
- package/src/api/controllers/user.js +2 -0
- package/src/api/routes/index.js +0 -2
- package/src/api/routes/tests/application.spec.js +2 -2
- package/src/api/routes/tests/auth.spec.js +1 -2
- package/src/constants/index.js +4 -0
- package/src/definitions/datasource.ts +3 -0
- package/src/integrations/googlesheets.ts +353 -0
- package/src/integrations/index.ts +3 -0
- package/src/integrations/utils.ts +18 -5
- package/src/middleware/authorized.js +64 -30
- package/src/middleware/currentapp.js +0 -9
- package/src/middleware/tests/authorized.spec.js +6 -4
- package/src/migrations/tests/usageQuotas/syncRows.spec.js +1 -1
- package/src/tests/utilities/TestConfiguration.js +10 -6
- package/src/threads/query.js +5 -0
- package/src/utilities/workerRequests.js +0 -23
- package/coverage/lcov-report/api/controllers/hosting.js.html +0 -146
- package/coverage/lcov-report/api/routes/hosting.js.html +0 -119
- package/dist/api/controllers/hosting.js +0 -30
- package/dist/api/routes/hosting.js +0 -11
- package/src/api/controllers/hosting.js +0 -22
- package/src/api/routes/hosting.js +0 -13
- package/src/api/routes/tests/hosting.spec.js +0 -36
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DatasourceFieldTypes,
|
|
3
|
+
Integration,
|
|
4
|
+
QueryJson,
|
|
5
|
+
QueryTypes,
|
|
6
|
+
} from "../definitions/datasource"
|
|
7
|
+
import { OAuth2Client } from "google-auth-library"
|
|
8
|
+
import { DatasourcePlus } from "./base/datasourcePlus"
|
|
9
|
+
import { Row, Table, TableSchema } from "../definitions/common"
|
|
10
|
+
import { buildExternalTableId } from "./utils"
|
|
11
|
+
import { DataSourceOperation, FieldTypes } from "../constants"
|
|
12
|
+
import { GoogleSpreadsheet } from "google-spreadsheet"
|
|
13
|
+
import { table } from "console"
|
|
14
|
+
|
|
15
|
+
module GoogleSheetsModule {
|
|
16
|
+
const { getGlobalDB } = require("@budibase/backend-core/tenancy")
|
|
17
|
+
const { getScopedConfig } = require("@budibase/backend-core/db")
|
|
18
|
+
const { Configs } = require("@budibase/backend-core/constants")
|
|
19
|
+
|
|
20
|
+
interface GoogleSheetsConfig {
|
|
21
|
+
spreadsheetId: string
|
|
22
|
+
auth: OAuthClientConfig
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface OAuthClientConfig {
|
|
26
|
+
appId: string
|
|
27
|
+
accessToken: string
|
|
28
|
+
refreshToken: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const SCHEMA: Integration = {
|
|
32
|
+
plus: true,
|
|
33
|
+
auth: {
|
|
34
|
+
type: "google",
|
|
35
|
+
},
|
|
36
|
+
relationships: false,
|
|
37
|
+
docs: "https://developers.google.com/sheets/api/quickstart/nodejs",
|
|
38
|
+
description:
|
|
39
|
+
"Create and collaborate on online spreadsheets in real-time and from any device. ",
|
|
40
|
+
friendlyName: "Google Sheets",
|
|
41
|
+
datasource: {
|
|
42
|
+
spreadsheetId: {
|
|
43
|
+
type: DatasourceFieldTypes.STRING,
|
|
44
|
+
required: true,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
query: {
|
|
48
|
+
create: {
|
|
49
|
+
type: QueryTypes.FIELDS,
|
|
50
|
+
fields: {
|
|
51
|
+
sheet: {
|
|
52
|
+
type: DatasourceFieldTypes.STRING,
|
|
53
|
+
required: true,
|
|
54
|
+
},
|
|
55
|
+
row: {
|
|
56
|
+
type: QueryTypes.JSON,
|
|
57
|
+
required: true,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
read: {
|
|
62
|
+
type: QueryTypes.FIELDS,
|
|
63
|
+
fields: {
|
|
64
|
+
sheet: {
|
|
65
|
+
type: DatasourceFieldTypes.STRING,
|
|
66
|
+
required: true,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
update: {
|
|
71
|
+
type: QueryTypes.FIELDS,
|
|
72
|
+
fields: {
|
|
73
|
+
sheet: {
|
|
74
|
+
type: DatasourceFieldTypes.STRING,
|
|
75
|
+
required: true,
|
|
76
|
+
},
|
|
77
|
+
rowIndex: {
|
|
78
|
+
type: DatasourceFieldTypes.STRING,
|
|
79
|
+
required: true,
|
|
80
|
+
},
|
|
81
|
+
row: {
|
|
82
|
+
type: QueryTypes.JSON,
|
|
83
|
+
required: true,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
delete: {
|
|
88
|
+
type: QueryTypes.FIELDS,
|
|
89
|
+
fields: {
|
|
90
|
+
sheet: {
|
|
91
|
+
type: DatasourceFieldTypes.STRING,
|
|
92
|
+
required: true,
|
|
93
|
+
},
|
|
94
|
+
rowIndex: {
|
|
95
|
+
type: DatasourceFieldTypes.NUMBER,
|
|
96
|
+
required: true,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
class GoogleSheetsIntegration implements DatasourcePlus {
|
|
104
|
+
private readonly config: GoogleSheetsConfig
|
|
105
|
+
private client: any
|
|
106
|
+
public tables: Record<string, Table> = {}
|
|
107
|
+
public schemaErrors: Record<string, string> = {}
|
|
108
|
+
|
|
109
|
+
constructor(config: GoogleSheetsConfig) {
|
|
110
|
+
this.config = config
|
|
111
|
+
const spreadsheetId = this.cleanSpreadsheetUrl(this.config.spreadsheetId)
|
|
112
|
+
this.client = new GoogleSpreadsheet(spreadsheetId)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Pull the spreadsheet ID out from a valid google sheets URL
|
|
117
|
+
* @param spreadsheetId - the URL or standard spreadsheetId of the google sheet
|
|
118
|
+
* @returns spreadsheet Id of the google sheet
|
|
119
|
+
*/
|
|
120
|
+
cleanSpreadsheetUrl(spreadsheetId: string) {
|
|
121
|
+
if (!spreadsheetId) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
"You must set a spreadsheet ID in your configuration to fetch tables."
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
const parts = spreadsheetId.split("/")
|
|
127
|
+
return parts.length > 5 ? parts[5] : spreadsheetId
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async connect() {
|
|
131
|
+
try {
|
|
132
|
+
// Initialise oAuth client
|
|
133
|
+
const db = getGlobalDB()
|
|
134
|
+
const googleConfig = await getScopedConfig(db, {
|
|
135
|
+
type: Configs.GOOGLE,
|
|
136
|
+
})
|
|
137
|
+
const oauthClient = new OAuth2Client({
|
|
138
|
+
clientId: googleConfig.clientID,
|
|
139
|
+
clientSecret: googleConfig.clientSecret,
|
|
140
|
+
})
|
|
141
|
+
oauthClient.credentials.access_token = this.config.auth.accessToken
|
|
142
|
+
oauthClient.credentials.refresh_token = this.config.auth.refreshToken
|
|
143
|
+
this.client.useOAuth2Client(oauthClient)
|
|
144
|
+
await this.client.loadInfo()
|
|
145
|
+
} catch (err) {
|
|
146
|
+
console.error("Error connecting to google sheets", err)
|
|
147
|
+
throw err
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async buildSchema(datasourceId: string) {
|
|
152
|
+
await this.connect()
|
|
153
|
+
const sheets = await this.client.sheetsByIndex
|
|
154
|
+
const tables: Record<string, Table> = {}
|
|
155
|
+
for (let sheet of sheets) {
|
|
156
|
+
// must fetch rows to determine schema
|
|
157
|
+
await sheet.getRows()
|
|
158
|
+
// build schema
|
|
159
|
+
const schema: TableSchema = {}
|
|
160
|
+
|
|
161
|
+
// build schema from headers
|
|
162
|
+
for (let header of sheet.headerValues) {
|
|
163
|
+
schema[header] = {
|
|
164
|
+
name: header,
|
|
165
|
+
type: FieldTypes.STRING,
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// create tables
|
|
170
|
+
tables[sheet.title] = {
|
|
171
|
+
_id: buildExternalTableId(datasourceId, sheet.title),
|
|
172
|
+
name: sheet.title,
|
|
173
|
+
primary: ["rowNumber"],
|
|
174
|
+
schema,
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
this.tables = tables
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async query(json: QueryJson) {
|
|
182
|
+
const sheet = json.endpoint.entityId
|
|
183
|
+
|
|
184
|
+
const handlers = {
|
|
185
|
+
[DataSourceOperation.CREATE]: () =>
|
|
186
|
+
this.create({ sheet, row: json.body }),
|
|
187
|
+
[DataSourceOperation.READ]: () => this.read({ sheet }),
|
|
188
|
+
[DataSourceOperation.UPDATE]: () =>
|
|
189
|
+
this.update({
|
|
190
|
+
// exclude the header row and zero index
|
|
191
|
+
rowIndex: json.extra?.idFilter?.equal?.rowNumber - 2,
|
|
192
|
+
sheet,
|
|
193
|
+
row: json.body,
|
|
194
|
+
}),
|
|
195
|
+
[DataSourceOperation.DELETE]: () =>
|
|
196
|
+
this.delete({
|
|
197
|
+
// exclude the header row and zero index
|
|
198
|
+
rowIndex: json.extra?.idFilter?.equal?.rowNumber - 2,
|
|
199
|
+
sheet,
|
|
200
|
+
}),
|
|
201
|
+
[DataSourceOperation.CREATE_TABLE]: () =>
|
|
202
|
+
this.createTable(json?.table?.name),
|
|
203
|
+
[DataSourceOperation.UPDATE_TABLE]: () => this.updateTable(json.table),
|
|
204
|
+
[DataSourceOperation.DELETE_TABLE]: () =>
|
|
205
|
+
this.deleteTable(json?.table?.name),
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const internalQueryMethod = handlers[json.endpoint.operation]
|
|
209
|
+
|
|
210
|
+
return await internalQueryMethod()
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
buildRowObject(headers: string[], values: string[], rowNumber: number) {
|
|
214
|
+
const rowObject: { rowNumber: number; [key: string]: any } = { rowNumber }
|
|
215
|
+
for (let i = 0; i < headers.length; i++) {
|
|
216
|
+
rowObject._id = rowNumber
|
|
217
|
+
rowObject[headers[i]] = values[i]
|
|
218
|
+
}
|
|
219
|
+
return rowObject
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async createTable(name?: string) {
|
|
223
|
+
try {
|
|
224
|
+
await this.connect()
|
|
225
|
+
const sheet = await this.client.addSheet({ title: name })
|
|
226
|
+
return sheet
|
|
227
|
+
} catch (err) {
|
|
228
|
+
console.error("Error creating new table in google sheets", err)
|
|
229
|
+
throw err
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async updateTable(table?: any) {
|
|
234
|
+
try {
|
|
235
|
+
await this.connect()
|
|
236
|
+
const sheet = await this.client.sheetsByTitle[table.name]
|
|
237
|
+
await sheet.loadHeaderRow()
|
|
238
|
+
|
|
239
|
+
if (table._rename) {
|
|
240
|
+
const headers = []
|
|
241
|
+
for (let header of sheet.headerValues) {
|
|
242
|
+
if (header === table._rename.old) {
|
|
243
|
+
headers.push(table._rename.updated)
|
|
244
|
+
} else {
|
|
245
|
+
headers.push(header)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
await sheet.setHeaderRow(headers)
|
|
249
|
+
} else {
|
|
250
|
+
let newField = Object.keys(table.schema).find(
|
|
251
|
+
key => !sheet.headerValues.includes(key)
|
|
252
|
+
)
|
|
253
|
+
await sheet.setHeaderRow([...sheet.headerValues, newField])
|
|
254
|
+
}
|
|
255
|
+
} catch (err) {
|
|
256
|
+
console.error("Error updating table in google sheets", err)
|
|
257
|
+
throw err
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async deleteTable(sheet: any) {
|
|
262
|
+
try {
|
|
263
|
+
await this.connect()
|
|
264
|
+
const sheetToDelete = await this.client.sheetsByTitle[sheet]
|
|
265
|
+
return await sheetToDelete.delete()
|
|
266
|
+
} catch (err) {
|
|
267
|
+
console.error("Error deleting table in google sheets", err)
|
|
268
|
+
throw err
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async create(query: { sheet: string; row: any }) {
|
|
273
|
+
try {
|
|
274
|
+
await this.connect()
|
|
275
|
+
const sheet = await this.client.sheetsByTitle[query.sheet]
|
|
276
|
+
const rowToInsert =
|
|
277
|
+
typeof query.row === "string" ? JSON.parse(query.row) : query.row
|
|
278
|
+
const row = await sheet.addRow(rowToInsert)
|
|
279
|
+
return [
|
|
280
|
+
this.buildRowObject(sheet.headerValues, row._rawData, row._rowNumber),
|
|
281
|
+
]
|
|
282
|
+
} catch (err) {
|
|
283
|
+
console.error("Error writing to google sheets", err)
|
|
284
|
+
throw err
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async read(query: { sheet: string }) {
|
|
289
|
+
try {
|
|
290
|
+
await this.connect()
|
|
291
|
+
const sheet = await this.client.sheetsByTitle[query.sheet]
|
|
292
|
+
const rows = await sheet.getRows()
|
|
293
|
+
const headerValues = sheet.headerValues
|
|
294
|
+
const response = []
|
|
295
|
+
for (let row of rows) {
|
|
296
|
+
response.push(
|
|
297
|
+
this.buildRowObject(headerValues, row._rawData, row._rowNumber)
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
return response
|
|
301
|
+
} catch (err) {
|
|
302
|
+
console.error("Error reading from google sheets", err)
|
|
303
|
+
throw err
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async update(query: { sheet: string; rowIndex: number; row: any }) {
|
|
308
|
+
try {
|
|
309
|
+
await this.connect()
|
|
310
|
+
const sheet = await this.client.sheetsByTitle[query.sheet]
|
|
311
|
+
const rows = await sheet.getRows()
|
|
312
|
+
const row = rows[query.rowIndex]
|
|
313
|
+
if (row) {
|
|
314
|
+
const updateValues = query.row
|
|
315
|
+
for (let key in updateValues) {
|
|
316
|
+
row[key] = updateValues[key]
|
|
317
|
+
}
|
|
318
|
+
await row.save()
|
|
319
|
+
return [
|
|
320
|
+
this.buildRowObject(
|
|
321
|
+
sheet.headerValues,
|
|
322
|
+
row._rawData,
|
|
323
|
+
row._rowNumber
|
|
324
|
+
),
|
|
325
|
+
]
|
|
326
|
+
} else {
|
|
327
|
+
throw new Error("Row does not exist.")
|
|
328
|
+
}
|
|
329
|
+
} catch (err) {
|
|
330
|
+
console.error("Error reading from google sheets", err)
|
|
331
|
+
throw err
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async delete(query: { sheet: string; rowIndex: number }) {
|
|
336
|
+
await this.connect()
|
|
337
|
+
const sheet = await this.client.sheetsByTitle[query.sheet]
|
|
338
|
+
const rows = await sheet.getRows()
|
|
339
|
+
const row = rows[query.rowIndex]
|
|
340
|
+
if (row) {
|
|
341
|
+
await row.delete()
|
|
342
|
+
return [{ deleted: query.rowIndex }]
|
|
343
|
+
} else {
|
|
344
|
+
throw new Error("Row does not exist.")
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
module.exports = {
|
|
350
|
+
schema: SCHEMA,
|
|
351
|
+
integration: GoogleSheetsIntegration,
|
|
352
|
+
}
|
|
353
|
+
}
|
|
@@ -9,6 +9,7 @@ const airtable = require("./airtable")
|
|
|
9
9
|
const mysql = require("./mysql")
|
|
10
10
|
const arangodb = require("./arangodb")
|
|
11
11
|
const rest = require("./rest")
|
|
12
|
+
const googlesheets = require("./googlesheets")
|
|
12
13
|
const { SourceNames } = require("../definitions/datasource")
|
|
13
14
|
|
|
14
15
|
const DEFINITIONS = {
|
|
@@ -23,6 +24,7 @@ const DEFINITIONS = {
|
|
|
23
24
|
[SourceNames.MYSQL]: mysql.schema,
|
|
24
25
|
[SourceNames.ARANGODB]: arangodb.schema,
|
|
25
26
|
[SourceNames.REST]: rest.schema,
|
|
27
|
+
[SourceNames.GOOGLE_SHEETS]: googlesheets.schema,
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
const INTEGRATIONS = {
|
|
@@ -37,6 +39,7 @@ const INTEGRATIONS = {
|
|
|
37
39
|
[SourceNames.MYSQL]: mysql.integration,
|
|
38
40
|
[SourceNames.ARANGODB]: arangodb.integration,
|
|
39
41
|
[SourceNames.REST]: rest.integration,
|
|
42
|
+
[SourceNames.GOOGLE_SHEETS]: googlesheets.integration,
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
// optionally add oracle integration if the oracle binary can be installed
|
|
@@ -153,8 +153,15 @@ export function isIsoDateString(str: string) {
|
|
|
153
153
|
* @param column The column to check, to see if it is a valid relationship.
|
|
154
154
|
* @param tableIds The IDs of the tables which currently exist.
|
|
155
155
|
*/
|
|
156
|
-
function shouldCopyRelationship(
|
|
157
|
-
|
|
156
|
+
function shouldCopyRelationship(
|
|
157
|
+
column: { type: string; tableId?: string },
|
|
158
|
+
tableIds: [string]
|
|
159
|
+
) {
|
|
160
|
+
return (
|
|
161
|
+
column.type === FieldTypes.LINK &&
|
|
162
|
+
column.tableId &&
|
|
163
|
+
tableIds.includes(column.tableId)
|
|
164
|
+
)
|
|
158
165
|
}
|
|
159
166
|
|
|
160
167
|
/**
|
|
@@ -165,9 +172,15 @@ function shouldCopyRelationship(column: { type: string, tableId?: string }, tabl
|
|
|
165
172
|
* @param column The column to check for options or boolean type.
|
|
166
173
|
* @param fetchedColumn The fetched column to check for the type in the external database.
|
|
167
174
|
*/
|
|
168
|
-
function shouldCopySpecialColumn(
|
|
169
|
-
|
|
170
|
-
|
|
175
|
+
function shouldCopySpecialColumn(
|
|
176
|
+
column: { type: string },
|
|
177
|
+
fetchedColumn: { type: string } | undefined
|
|
178
|
+
) {
|
|
179
|
+
return (
|
|
180
|
+
column.type === FieldTypes.OPTIONS ||
|
|
181
|
+
((!fetchedColumn || fetchedColumn.type === FieldTypes.NUMBER) &&
|
|
182
|
+
column.type === FieldTypes.BOOLEAN)
|
|
183
|
+
)
|
|
171
184
|
}
|
|
172
185
|
|
|
173
186
|
/**
|
|
@@ -9,11 +9,59 @@ const {
|
|
|
9
9
|
} = require("@budibase/backend-core/permissions")
|
|
10
10
|
const builderMiddleware = require("./builder")
|
|
11
11
|
const { isWebhookEndpoint } = require("./utils")
|
|
12
|
+
const { buildCsrfMiddleware } = require("@budibase/backend-core/auth")
|
|
12
13
|
|
|
13
14
|
function hasResource(ctx) {
|
|
14
15
|
return ctx.resourceId != null
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
const csrf = buildCsrfMiddleware()
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Apply authorization to the requested resource:
|
|
22
|
+
* - If this is a builder resource the user must be a builder.
|
|
23
|
+
* - Builders can access all resources.
|
|
24
|
+
* - Otherwise the user must have the required role.
|
|
25
|
+
*/
|
|
26
|
+
const checkAuthorized = async (ctx, resourceRoles, permType, permLevel) => {
|
|
27
|
+
// check if this is a builder api and the user is not a builder
|
|
28
|
+
const isBuilder = ctx.user && ctx.user.builder && ctx.user.builder.global
|
|
29
|
+
const isBuilderApi = permType === PermissionTypes.BUILDER
|
|
30
|
+
if (isBuilderApi && !isBuilder) {
|
|
31
|
+
return ctx.throw(403, "Not Authorized")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// check for resource authorization
|
|
35
|
+
if (!isBuilder) {
|
|
36
|
+
await checkAuthorizedResource(ctx, resourceRoles, permType, permLevel)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const checkAuthorizedResource = async (
|
|
41
|
+
ctx,
|
|
42
|
+
resourceRoles,
|
|
43
|
+
permType,
|
|
44
|
+
permLevel
|
|
45
|
+
) => {
|
|
46
|
+
// get the user's roles
|
|
47
|
+
const roleId = ctx.roleId || BUILTIN_ROLE_IDS.PUBLIC
|
|
48
|
+
const userRoles = await getUserRoleHierarchy(ctx.appId, roleId, {
|
|
49
|
+
idOnly: false,
|
|
50
|
+
})
|
|
51
|
+
const permError = "User does not have permission"
|
|
52
|
+
// check if the user has the required role
|
|
53
|
+
if (resourceRoles.length > 0) {
|
|
54
|
+
// deny access if the user doesn't have the required resource role
|
|
55
|
+
const found = userRoles.find(role => resourceRoles.indexOf(role._id) !== -1)
|
|
56
|
+
if (!found) {
|
|
57
|
+
ctx.throw(403, permError)
|
|
58
|
+
}
|
|
59
|
+
// fallback to the base permissions when no resource roles are found
|
|
60
|
+
} else if (!doesHaveBasePermission(permType, permLevel, userRoles)) {
|
|
61
|
+
ctx.throw(403, permError)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
17
65
|
module.exports =
|
|
18
66
|
(permType, permLevel = null) =>
|
|
19
67
|
async (ctx, next) => {
|
|
@@ -31,40 +79,26 @@ module.exports =
|
|
|
31
79
|
// to find API endpoints which are builder focused
|
|
32
80
|
await builderMiddleware(ctx, permType)
|
|
33
81
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (isBuilder) {
|
|
39
|
-
return next()
|
|
40
|
-
} else if (isBuilderApi && !isBuilder) {
|
|
41
|
-
return ctx.throw(403, "Not Authorized")
|
|
82
|
+
// get the resource roles
|
|
83
|
+
let resourceRoles = []
|
|
84
|
+
if (ctx.appId && hasResource(ctx)) {
|
|
85
|
+
resourceRoles = await getRequiredResourceRole(ctx.appId, permLevel, ctx)
|
|
42
86
|
}
|
|
43
87
|
|
|
44
|
-
//
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
})
|
|
49
|
-
const permError = "User does not have permission"
|
|
50
|
-
let possibleRoleIds = []
|
|
51
|
-
if (hasResource(ctx)) {
|
|
52
|
-
possibleRoleIds = await getRequiredResourceRole(ctx.appId, permLevel, ctx)
|
|
53
|
-
}
|
|
54
|
-
// check if we found a role, if not fallback to base permissions
|
|
55
|
-
if (possibleRoleIds.length > 0) {
|
|
56
|
-
const found = hierarchy.find(
|
|
57
|
-
role => possibleRoleIds.indexOf(role._id) !== -1
|
|
58
|
-
)
|
|
59
|
-
return found ? next() : ctx.throw(403, permError)
|
|
60
|
-
} else if (!doesHaveBasePermission(permType, permLevel, hierarchy)) {
|
|
61
|
-
ctx.throw(403, permError)
|
|
88
|
+
// if the resource is public, proceed
|
|
89
|
+
const isPublicResource = resourceRoles.includes(BUILTIN_ROLE_IDS.PUBLIC)
|
|
90
|
+
if (isPublicResource) {
|
|
91
|
+
return next()
|
|
62
92
|
}
|
|
63
93
|
|
|
64
|
-
//
|
|
65
|
-
if (!
|
|
66
|
-
ctx.throw(403, "Session not authenticated")
|
|
94
|
+
// check authenticated
|
|
95
|
+
if (!ctx.isAuthenticated) {
|
|
96
|
+
return ctx.throw(403, "Session not authenticated")
|
|
67
97
|
}
|
|
68
98
|
|
|
69
|
-
|
|
99
|
+
// check authorized
|
|
100
|
+
await checkAuthorized(ctx, resourceRoles, permType, permLevel)
|
|
101
|
+
|
|
102
|
+
// csrf protection
|
|
103
|
+
return csrf(ctx, next)
|
|
70
104
|
}
|
|
@@ -47,15 +47,6 @@ module.exports = async (ctx, next) => {
|
|
|
47
47
|
(!ctx.user || !ctx.user.builder || !ctx.user.builder.global)
|
|
48
48
|
) {
|
|
49
49
|
clearCookie(ctx, Cookies.CurrentApp)
|
|
50
|
-
// have to set the return url on the server side as client side is not available
|
|
51
|
-
setCookie(ctx, ctx.url, Cookies.RETURN_URL, {
|
|
52
|
-
// don't sign so the browser can easily read
|
|
53
|
-
sign: false,
|
|
54
|
-
// use the request domain to match how ui handles the return url cookie.
|
|
55
|
-
// it's important we don't use the shared domain here as the builder
|
|
56
|
-
// can't delete from it without awareness of the domain.
|
|
57
|
-
requestDomain: true,
|
|
58
|
-
})
|
|
59
50
|
return ctx.redirect("/")
|
|
60
51
|
}
|
|
61
52
|
|
|
@@ -17,6 +17,7 @@ class TestConfiguration {
|
|
|
17
17
|
this.middleware = authorizedMiddleware(role)
|
|
18
18
|
this.next = jest.fn()
|
|
19
19
|
this.throw = jest.fn()
|
|
20
|
+
this.headers = {}
|
|
20
21
|
this.ctx = {
|
|
21
22
|
headers: {},
|
|
22
23
|
request: {
|
|
@@ -25,7 +26,8 @@ class TestConfiguration {
|
|
|
25
26
|
appId: "",
|
|
26
27
|
auth: {},
|
|
27
28
|
next: this.next,
|
|
28
|
-
throw: this.throw
|
|
29
|
+
throw: this.throw,
|
|
30
|
+
get: (name) => this.headers[name],
|
|
29
31
|
}
|
|
30
32
|
}
|
|
31
33
|
|
|
@@ -46,7 +48,7 @@ class TestConfiguration {
|
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
setAuthenticated(isAuthed) {
|
|
49
|
-
this.ctx.
|
|
51
|
+
this.ctx.isAuthenticated = isAuthed
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
setRequestUrl(url) {
|
|
@@ -107,7 +109,7 @@ describe("Authorization middleware", () => {
|
|
|
107
109
|
expect(config.next).toHaveBeenCalled()
|
|
108
110
|
})
|
|
109
111
|
|
|
110
|
-
it("throws if the user
|
|
112
|
+
it("throws if the user does not have builder permissions", async () => {
|
|
111
113
|
config.setEnvironment(false)
|
|
112
114
|
config.setMiddlewareRequiredPermission(PermissionTypes.BUILDER)
|
|
113
115
|
config.setUser({
|
|
@@ -133,7 +135,7 @@ describe("Authorization middleware", () => {
|
|
|
133
135
|
expect(config.next).toHaveBeenCalled()
|
|
134
136
|
})
|
|
135
137
|
|
|
136
|
-
it("throws if the user session is not authenticated
|
|
138
|
+
it("throws if the user session is not authenticated", async () => {
|
|
137
139
|
config.setUser({
|
|
138
140
|
role: {
|
|
139
141
|
_id: ""
|
|
@@ -22,10 +22,12 @@ const { getGlobalDB } = require("@budibase/backend-core/tenancy")
|
|
|
22
22
|
const { createASession } = require("@budibase/backend-core/sessions")
|
|
23
23
|
const { user: userCache } = require("@budibase/backend-core/cache")
|
|
24
24
|
const CouchDB = require("../../db")
|
|
25
|
+
const newid = require("../../db/newid")
|
|
25
26
|
core.init(CouchDB)
|
|
26
27
|
|
|
27
28
|
const GLOBAL_USER_ID = "us_uuid1"
|
|
28
29
|
const EMAIL = "babs@babs.com"
|
|
30
|
+
const CSRF_TOKEN = "e3727778-7af0-4226-b5eb-f43cbe60a306"
|
|
29
31
|
|
|
30
32
|
class TestConfiguration {
|
|
31
33
|
constructor(openServer = true) {
|
|
@@ -85,7 +87,11 @@ class TestConfiguration {
|
|
|
85
87
|
roles: roles || {},
|
|
86
88
|
tenantId: TENANT_ID,
|
|
87
89
|
}
|
|
88
|
-
await createASession(id, {
|
|
90
|
+
await createASession(id, {
|
|
91
|
+
sessionId: "sessionid",
|
|
92
|
+
tenantId: TENANT_ID,
|
|
93
|
+
csrfToken: CSRF_TOKEN,
|
|
94
|
+
})
|
|
89
95
|
if (builder) {
|
|
90
96
|
user.builder = { global: true }
|
|
91
97
|
} else {
|
|
@@ -98,7 +104,8 @@ class TestConfiguration {
|
|
|
98
104
|
}
|
|
99
105
|
}
|
|
100
106
|
|
|
101
|
-
|
|
107
|
+
// use a new id as the name to avoid name collisions
|
|
108
|
+
async init(appName = newid()) {
|
|
102
109
|
await this.globalUser()
|
|
103
110
|
return this.createApp(appName)
|
|
104
111
|
}
|
|
@@ -131,6 +138,7 @@ class TestConfiguration {
|
|
|
131
138
|
`${Cookies.Auth}=${authToken}`,
|
|
132
139
|
`${Cookies.CurrentApp}=${appToken}`,
|
|
133
140
|
],
|
|
141
|
+
[Headers.CSRF_TOKEN]: CSRF_TOKEN,
|
|
134
142
|
}
|
|
135
143
|
if (this.appId) {
|
|
136
144
|
headers[Headers.APP_ID] = this.appId
|
|
@@ -424,10 +432,6 @@ class TestConfiguration {
|
|
|
424
432
|
roles: { [this.prodAppId]: roleId },
|
|
425
433
|
})
|
|
426
434
|
}
|
|
427
|
-
await createASession(userId, {
|
|
428
|
-
sessionId: "sessionid",
|
|
429
|
-
tenantId: TENANT_ID,
|
|
430
|
-
})
|
|
431
435
|
// have to fake this
|
|
432
436
|
const auth = {
|
|
433
437
|
userId,
|
package/src/threads/query.js
CHANGED
|
@@ -58,29 +58,6 @@ exports.sendSmtpEmail = async (to, from, subject, contents, automation) => {
|
|
|
58
58
|
return response.json()
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
exports.getDeployedApps = async () => {
|
|
62
|
-
try {
|
|
63
|
-
const response = await fetch(
|
|
64
|
-
checkSlashesInUrl(env.WORKER_URL + `/api/apps`),
|
|
65
|
-
request(null, {
|
|
66
|
-
method: "GET",
|
|
67
|
-
})
|
|
68
|
-
)
|
|
69
|
-
const json = await response.json()
|
|
70
|
-
const apps = {}
|
|
71
|
-
for (let [key, value] of Object.entries(json)) {
|
|
72
|
-
if (value.url) {
|
|
73
|
-
value.url = value.url.toLowerCase()
|
|
74
|
-
apps[key.toLowerCase()] = value
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return apps
|
|
78
|
-
} catch (err) {
|
|
79
|
-
// error, cannot determine deployed apps, don't stop app creation - sort this later
|
|
80
|
-
return {}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
61
|
exports.getGlobalSelf = async (ctx, appId = null) => {
|
|
85
62
|
const endpoint = `/api/global/users/self`
|
|
86
63
|
const response = await fetch(
|