@budibase/server 2.7.21-alpha.1 → 2.7.21
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.77561524.js → index.1066b334.js} +347 -347
- package/builder/assets/{index.36462e95.css → index.d9b46807.css} +1 -1
- package/builder/index.html +2 -2
- package/dist/automation.js +180 -421
- package/dist/automation.js.map +3 -3
- package/dist/index.js +284 -533
- package/dist/index.js.map +3 -3
- package/dist/query.js +142 -317
- package/dist/query.js.map +3 -3
- package/package.json +11 -11
- package/src/api/controllers/application.ts +1 -1
- package/src/api/controllers/backup.ts +8 -22
- package/src/api/controllers/datasource.ts +24 -41
- package/src/api/controllers/role.ts +5 -5
- package/src/api/controllers/routing.ts +3 -3
- package/src/api/routes/backup.ts +1 -1
- package/src/api/routes/tests/backup.spec.ts +2 -18
- package/src/automations/steps/sendSmtpEmail.ts +4 -55
- package/src/automations/tests/sendSmtpEmail.spec.js +71 -0
- package/src/events/docUpdates/syncUsers.ts +0 -4
- package/src/integrations/googlesheets.ts +18 -35
- package/src/integrations/mongodb.ts +2 -4
- package/src/integrations/postgres.ts +3 -21
- package/src/middleware/currentapp.ts +1 -1
- package/src/sdk/app/backups/exports.ts +5 -33
- package/src/sdk/app/backups/imports.ts +1 -21
- package/src/sdk/app/datasources/datasources.ts +0 -1
- package/src/utilities/workerRequests.ts +9 -20
- package/src/automations/tests/sendSmtpEmail.spec.ts +0 -74
|
@@ -20,7 +20,7 @@ import Sql from "./base/sql"
|
|
|
20
20
|
import { PostgresColumn } from "./base/types"
|
|
21
21
|
import { escapeDangerousCharacters } from "../utilities"
|
|
22
22
|
|
|
23
|
-
import { Client,
|
|
23
|
+
import { Client, types } from "pg"
|
|
24
24
|
|
|
25
25
|
// Return "date" and "timestamp" types as plain strings.
|
|
26
26
|
// This lets us reference the original stored timezone.
|
|
@@ -42,8 +42,6 @@ interface PostgresConfig {
|
|
|
42
42
|
schema: string
|
|
43
43
|
ssl?: boolean
|
|
44
44
|
ca?: string
|
|
45
|
-
clientKey?: string
|
|
46
|
-
clientCert?: string
|
|
47
45
|
rejectUnauthorized?: boolean
|
|
48
46
|
}
|
|
49
47
|
|
|
@@ -100,19 +98,6 @@ const SCHEMA: Integration = {
|
|
|
100
98
|
required: false,
|
|
101
99
|
},
|
|
102
100
|
ca: {
|
|
103
|
-
display: "Server CA",
|
|
104
|
-
type: DatasourceFieldType.LONGFORM,
|
|
105
|
-
default: false,
|
|
106
|
-
required: false,
|
|
107
|
-
},
|
|
108
|
-
clientKey: {
|
|
109
|
-
display: "Client key",
|
|
110
|
-
type: DatasourceFieldType.LONGFORM,
|
|
111
|
-
default: false,
|
|
112
|
-
required: false,
|
|
113
|
-
},
|
|
114
|
-
clientCert: {
|
|
115
|
-
display: "Client cert",
|
|
116
101
|
type: DatasourceFieldType.LONGFORM,
|
|
117
102
|
default: false,
|
|
118
103
|
required: false,
|
|
@@ -159,14 +144,12 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
|
|
|
159
144
|
super(SqlClient.POSTGRES)
|
|
160
145
|
this.config = config
|
|
161
146
|
|
|
162
|
-
let newConfig
|
|
147
|
+
let newConfig = {
|
|
163
148
|
...this.config,
|
|
164
149
|
ssl: this.config.ssl
|
|
165
150
|
? {
|
|
166
151
|
rejectUnauthorized: this.config.rejectUnauthorized,
|
|
167
152
|
ca: this.config.ca,
|
|
168
|
-
key: this.config.clientKey,
|
|
169
|
-
cert: this.config.clientCert,
|
|
170
153
|
}
|
|
171
154
|
: undefined,
|
|
172
155
|
}
|
|
@@ -339,8 +322,7 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
|
|
|
339
322
|
await this.openConnection()
|
|
340
323
|
const columnsResponse: { rows: PostgresColumn[] } =
|
|
341
324
|
await this.client.query(this.COLUMNS_SQL)
|
|
342
|
-
|
|
343
|
-
return [...new Set(names)]
|
|
325
|
+
return columnsResponse.rows.map(row => row.table_name)
|
|
344
326
|
} finally {
|
|
345
327
|
await this.closeConnection()
|
|
346
328
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { db as dbCore,
|
|
1
|
+
import { db as dbCore, objectStore } from "@budibase/backend-core"
|
|
2
2
|
import { budibaseTempDir } from "../../../utilities/budibaseDir"
|
|
3
3
|
import { streamFile, createTempFolder } from "../../../utilities/fileSystem"
|
|
4
4
|
import { ObjectStoreBuckets } from "../../../constants"
|
|
@@ -18,8 +18,7 @@ import { join } from "path"
|
|
|
18
18
|
import env from "../../../environment"
|
|
19
19
|
|
|
20
20
|
const uuid = require("uuid/v4")
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const tar = require("tar")
|
|
23
22
|
const MemoryStream = require("memorystream")
|
|
24
23
|
|
|
25
24
|
interface DBDumpOpts {
|
|
@@ -31,18 +30,16 @@ interface ExportOpts extends DBDumpOpts {
|
|
|
31
30
|
tar?: boolean
|
|
32
31
|
excludeRows?: boolean
|
|
33
32
|
excludeLogs?: boolean
|
|
34
|
-
encryptPassword?: string
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
function tarFilesToTmp(tmpDir: string, files: string[]) {
|
|
38
|
-
const
|
|
39
|
-
const exportFile = join(budibaseTempDir(), fileName)
|
|
36
|
+
const exportFile = join(budibaseTempDir(), `${uuid()}.tar.gz`)
|
|
40
37
|
tar.create(
|
|
41
38
|
{
|
|
42
39
|
sync: true,
|
|
43
40
|
gzip: true,
|
|
44
41
|
file: exportFile,
|
|
45
|
-
|
|
42
|
+
recursive: true,
|
|
46
43
|
cwd: tmpDir,
|
|
47
44
|
},
|
|
48
45
|
files
|
|
@@ -127,7 +124,6 @@ export async function exportApp(appId: string, config?: ExportOpts) {
|
|
|
127
124
|
)
|
|
128
125
|
}
|
|
129
126
|
}
|
|
130
|
-
|
|
131
127
|
const downloadedPath = join(tmpPath, appPath)
|
|
132
128
|
if (fs.existsSync(downloadedPath)) {
|
|
133
129
|
const allFiles = fs.readdirSync(downloadedPath)
|
|
@@ -145,27 +141,12 @@ export async function exportApp(appId: string, config?: ExportOpts) {
|
|
|
145
141
|
filter: defineFilter(config?.excludeRows, config?.excludeLogs),
|
|
146
142
|
exportPath: dbPath,
|
|
147
143
|
})
|
|
148
|
-
|
|
149
|
-
if (config?.encryptPassword) {
|
|
150
|
-
for (let file of fs.readdirSync(tmpPath)) {
|
|
151
|
-
const path = join(tmpPath, file)
|
|
152
|
-
|
|
153
|
-
await encryption.encryptFile(
|
|
154
|
-
{ dir: tmpPath, filename: file },
|
|
155
|
-
config.encryptPassword
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
fs.rmSync(path)
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
144
|
// if tar requested, return where the tarball is
|
|
163
145
|
if (config?.tar) {
|
|
164
146
|
// now the tmpPath contains both the DB export and attachments, tar this
|
|
165
147
|
const tarPath = tarFilesToTmp(tmpPath, fs.readdirSync(tmpPath))
|
|
166
148
|
// cleanup the tmp export files as tarball returned
|
|
167
149
|
fs.rmSync(tmpPath, { recursive: true, force: true })
|
|
168
|
-
|
|
169
150
|
return tarPath
|
|
170
151
|
}
|
|
171
152
|
// tar not requested, turn the directory where export is
|
|
@@ -180,20 +161,11 @@ export async function exportApp(appId: string, config?: ExportOpts) {
|
|
|
180
161
|
* @param {boolean} excludeRows Flag to state whether the export should include data.
|
|
181
162
|
* @returns {*} a readable stream of the backup which is written in real time
|
|
182
163
|
*/
|
|
183
|
-
export async function streamExportApp({
|
|
184
|
-
appId,
|
|
185
|
-
excludeRows,
|
|
186
|
-
encryptPassword,
|
|
187
|
-
}: {
|
|
188
|
-
appId: string
|
|
189
|
-
excludeRows: boolean
|
|
190
|
-
encryptPassword?: string
|
|
191
|
-
}) {
|
|
164
|
+
export async function streamExportApp(appId: string, excludeRows: boolean) {
|
|
192
165
|
const tmpPath = await exportApp(appId, {
|
|
193
166
|
excludeRows,
|
|
194
167
|
excludeLogs: true,
|
|
195
168
|
tar: true,
|
|
196
|
-
encryptPassword,
|
|
197
169
|
})
|
|
198
170
|
return streamFile(tmpPath)
|
|
199
171
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { db as dbCore,
|
|
1
|
+
import { db as dbCore, objectStore } from "@budibase/backend-core"
|
|
2
2
|
import { Database, Row } from "@budibase/types"
|
|
3
3
|
import { getAutomationParams, TABLE_ROW_PREFIX } from "../../../db/utils"
|
|
4
4
|
import { budibaseTempDir } from "../../../utilities/budibaseDir"
|
|
@@ -20,7 +20,6 @@ type TemplateType = {
|
|
|
20
20
|
file?: {
|
|
21
21
|
type: string
|
|
22
22
|
path: string
|
|
23
|
-
password?: string
|
|
24
23
|
}
|
|
25
24
|
key?: string
|
|
26
25
|
}
|
|
@@ -124,22 +123,6 @@ export function untarFile(file: { path: string }) {
|
|
|
124
123
|
return tmpPath
|
|
125
124
|
}
|
|
126
125
|
|
|
127
|
-
async function decryptFiles(path: string, password: string) {
|
|
128
|
-
try {
|
|
129
|
-
for (let file of fs.readdirSync(path)) {
|
|
130
|
-
const inputPath = join(path, file)
|
|
131
|
-
const outputPath = inputPath.replace(/\.enc$/, "")
|
|
132
|
-
await encryption.decryptFile(inputPath, outputPath, password)
|
|
133
|
-
fs.rmSync(inputPath)
|
|
134
|
-
}
|
|
135
|
-
} catch (err: any) {
|
|
136
|
-
if (err.message === "incorrect header check") {
|
|
137
|
-
throw new Error("File cannot be imported")
|
|
138
|
-
}
|
|
139
|
-
throw err
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
126
|
export function getGlobalDBFile(tmpPath: string) {
|
|
144
127
|
return fs.readFileSync(join(tmpPath, GLOBAL_DB_EXPORT_FILE), "utf8")
|
|
145
128
|
}
|
|
@@ -160,9 +143,6 @@ export async function importApp(
|
|
|
160
143
|
template.file && fs.lstatSync(template.file.path).isDirectory()
|
|
161
144
|
if (template.file && (isTar || isDirectory)) {
|
|
162
145
|
const tmpPath = isTar ? untarFile(template.file) : template.file.path
|
|
163
|
-
if (isTar && template.file.password) {
|
|
164
|
-
await decryptFiles(tmpPath, template.file.password)
|
|
165
|
-
}
|
|
166
146
|
const contents = fs.readdirSync(tmpPath)
|
|
167
147
|
// have to handle object import
|
|
168
148
|
if (contents.length) {
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
env as coreEnv,
|
|
10
10
|
} from "@budibase/backend-core"
|
|
11
11
|
import { updateAppRole } from "./global"
|
|
12
|
-
import { BBContext, User
|
|
12
|
+
import { BBContext, User } from "@budibase/types"
|
|
13
13
|
|
|
14
14
|
export function request(ctx?: BBContext, request?: any) {
|
|
15
15
|
if (!request.headers) {
|
|
@@ -65,25 +65,15 @@ async function checkResponse(
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// have to pass in the tenant ID as this could be coming from an automation
|
|
68
|
-
export async function sendSmtpEmail(
|
|
69
|
-
to,
|
|
70
|
-
from,
|
|
71
|
-
subject,
|
|
72
|
-
contents,
|
|
73
|
-
cc,
|
|
74
|
-
bcc,
|
|
75
|
-
automation,
|
|
76
|
-
invite,
|
|
77
|
-
}: {
|
|
78
|
-
to: string
|
|
79
|
-
from: string
|
|
80
|
-
subject: string
|
|
81
|
-
contents: string
|
|
82
|
-
cc: string
|
|
83
|
-
bcc: string
|
|
68
|
+
export async function sendSmtpEmail(
|
|
69
|
+
to: string,
|
|
70
|
+
from: string,
|
|
71
|
+
subject: string,
|
|
72
|
+
contents: string,
|
|
73
|
+
cc: string,
|
|
74
|
+
bcc: string,
|
|
84
75
|
automation: boolean
|
|
85
|
-
|
|
86
|
-
}) {
|
|
76
|
+
) {
|
|
87
77
|
// tenant ID will be set in header
|
|
88
78
|
const response = await fetch(
|
|
89
79
|
checkSlashesInUrl(env.WORKER_URL + `/api/global/email/send`),
|
|
@@ -98,7 +88,6 @@ export async function sendSmtpEmail({
|
|
|
98
88
|
bcc,
|
|
99
89
|
purpose: "custom",
|
|
100
90
|
automation,
|
|
101
|
-
invite,
|
|
102
91
|
},
|
|
103
92
|
})
|
|
104
93
|
)
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import * as workerRequests from "../../utilities/workerRequests"
|
|
2
|
-
|
|
3
|
-
jest.mock("../../utilities/workerRequests", () => ({
|
|
4
|
-
sendSmtpEmail: jest.fn(),
|
|
5
|
-
}))
|
|
6
|
-
|
|
7
|
-
function generateResponse(to: string, from: string) {
|
|
8
|
-
return {
|
|
9
|
-
success: true,
|
|
10
|
-
response: {
|
|
11
|
-
accepted: [to],
|
|
12
|
-
envelope: {
|
|
13
|
-
from: from,
|
|
14
|
-
to: [to],
|
|
15
|
-
},
|
|
16
|
-
message: `Email sent to ${to}.`,
|
|
17
|
-
},
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const setup = require("./utilities")
|
|
22
|
-
|
|
23
|
-
describe("test the outgoing webhook action", () => {
|
|
24
|
-
let inputs
|
|
25
|
-
let config = setup.getConfig()
|
|
26
|
-
beforeAll(async () => {
|
|
27
|
-
await config.init()
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
afterAll(setup.afterAll)
|
|
31
|
-
|
|
32
|
-
it("should be able to run the action", async () => {
|
|
33
|
-
jest
|
|
34
|
-
.spyOn(workerRequests, "sendSmtpEmail")
|
|
35
|
-
.mockImplementationOnce(async () =>
|
|
36
|
-
generateResponse("user1@test.com", "admin@test.com")
|
|
37
|
-
)
|
|
38
|
-
const invite = {
|
|
39
|
-
startTime: new Date(),
|
|
40
|
-
endTime: new Date(),
|
|
41
|
-
summary: "summary",
|
|
42
|
-
location: "location",
|
|
43
|
-
url: "url",
|
|
44
|
-
}
|
|
45
|
-
inputs = {
|
|
46
|
-
to: "user1@test.com",
|
|
47
|
-
from: "admin@test.com",
|
|
48
|
-
subject: "hello",
|
|
49
|
-
contents: "testing",
|
|
50
|
-
cc: "cc",
|
|
51
|
-
bcc: "bcc",
|
|
52
|
-
addInvite: true,
|
|
53
|
-
...invite,
|
|
54
|
-
}
|
|
55
|
-
let resp = generateResponse(inputs.to, inputs.from)
|
|
56
|
-
const res = await setup.runStep(
|
|
57
|
-
setup.actions.SEND_EMAIL_SMTP.stepId,
|
|
58
|
-
inputs
|
|
59
|
-
)
|
|
60
|
-
expect(res.response).toEqual(resp)
|
|
61
|
-
expect(res.success).toEqual(true)
|
|
62
|
-
expect(workerRequests.sendSmtpEmail).toHaveBeenCalledTimes(1)
|
|
63
|
-
expect(workerRequests.sendSmtpEmail).toHaveBeenCalledWith({
|
|
64
|
-
to: "user1@test.com",
|
|
65
|
-
from: "admin@test.com",
|
|
66
|
-
subject: "hello",
|
|
67
|
-
contents: "testing",
|
|
68
|
-
cc: "cc",
|
|
69
|
-
bcc: "bcc",
|
|
70
|
-
invite,
|
|
71
|
-
automation: true,
|
|
72
|
-
})
|
|
73
|
-
})
|
|
74
|
-
})
|