@budibase/worker 2.23.11 → 2.23.12
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/worker",
|
|
3
3
|
"email": "hi@budibase.com",
|
|
4
|
-
"version": "2.23.
|
|
4
|
+
"version": "2.23.12",
|
|
5
5
|
"description": "Budibase background service",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"repository": {
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"author": "Budibase",
|
|
38
38
|
"license": "GPL-3.0",
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@budibase/backend-core": "2.23.
|
|
41
|
-
"@budibase/pro": "2.23.
|
|
42
|
-
"@budibase/string-templates": "2.23.
|
|
43
|
-
"@budibase/types": "2.23.
|
|
40
|
+
"@budibase/backend-core": "2.23.12",
|
|
41
|
+
"@budibase/pro": "2.23.12",
|
|
42
|
+
"@budibase/string-templates": "2.23.12",
|
|
43
|
+
"@budibase/types": "2.23.12",
|
|
44
44
|
"@koa/router": "8.0.8",
|
|
45
45
|
"@techpass/passport-openidconnect": "0.3.2",
|
|
46
46
|
"@types/global-agent": "2.1.1",
|
|
@@ -108,5 +108,5 @@
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
},
|
|
111
|
-
"gitHead": "
|
|
111
|
+
"gitHead": "35dcb2465a0a116c4a6169a01b6154990747627a"
|
|
112
112
|
}
|
|
@@ -15,6 +15,7 @@ export async function sendEmail(ctx: BBContext) {
|
|
|
15
15
|
bcc,
|
|
16
16
|
automation,
|
|
17
17
|
invite,
|
|
18
|
+
attachments,
|
|
18
19
|
} = ctx.request.body
|
|
19
20
|
let user: any
|
|
20
21
|
if (userId) {
|
|
@@ -31,6 +32,7 @@ export async function sendEmail(ctx: BBContext) {
|
|
|
31
32
|
bcc,
|
|
32
33
|
automation,
|
|
33
34
|
invite,
|
|
35
|
+
attachments,
|
|
34
36
|
})
|
|
35
37
|
ctx.body = {
|
|
36
38
|
...response,
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
jest.unmock("node-fetch")
|
|
2
|
+
jest.unmock("aws-sdk")
|
|
2
3
|
import { TestConfiguration } from "../../../../tests"
|
|
3
4
|
import { EmailTemplatePurpose } from "../../../../constants"
|
|
5
|
+
import { objectStoreTestProviders } from "@budibase/backend-core/tests"
|
|
6
|
+
import { objectStore } from "@budibase/backend-core"
|
|
7
|
+
import tk from "timekeeper"
|
|
8
|
+
import { EmailAttachment } from "@budibase/types"
|
|
4
9
|
|
|
5
|
-
const nodemailer = require("nodemailer")
|
|
6
10
|
const fetch = require("node-fetch")
|
|
7
11
|
|
|
12
|
+
const nodemailer = require("nodemailer")
|
|
13
|
+
|
|
8
14
|
// for the real email tests give them a long time to try complete/fail
|
|
9
15
|
jest.setTimeout(30000)
|
|
10
16
|
|
|
@@ -12,14 +18,20 @@ describe("/api/global/email", () => {
|
|
|
12
18
|
const config = new TestConfiguration()
|
|
13
19
|
|
|
14
20
|
beforeAll(async () => {
|
|
21
|
+
tk.reset()
|
|
22
|
+
await objectStoreTestProviders.minio.start()
|
|
15
23
|
await config.beforeAll()
|
|
16
24
|
})
|
|
17
25
|
|
|
18
26
|
afterAll(async () => {
|
|
27
|
+
await objectStoreTestProviders.minio.stop()
|
|
19
28
|
await config.afterAll()
|
|
20
29
|
})
|
|
21
30
|
|
|
22
|
-
async function sendRealEmail(
|
|
31
|
+
async function sendRealEmail(
|
|
32
|
+
purpose: string,
|
|
33
|
+
attachments?: EmailAttachment[]
|
|
34
|
+
) {
|
|
23
35
|
let response, text
|
|
24
36
|
try {
|
|
25
37
|
const timeout = () =>
|
|
@@ -35,8 +47,14 @@ describe("/api/global/email", () => {
|
|
|
35
47
|
)
|
|
36
48
|
await Promise.race([config.saveEtherealSmtpConfig(), timeout()])
|
|
37
49
|
await Promise.race([config.saveSettingsConfig(), timeout()])
|
|
38
|
-
|
|
39
|
-
|
|
50
|
+
let res
|
|
51
|
+
if (attachments) {
|
|
52
|
+
res = await config.api.emails
|
|
53
|
+
.sendEmail(purpose, attachments)
|
|
54
|
+
.timeout(20000)
|
|
55
|
+
} else {
|
|
56
|
+
res = await config.api.emails.sendEmail(purpose).timeout(20000)
|
|
57
|
+
}
|
|
40
58
|
// ethereal hiccup, can't test right now
|
|
41
59
|
if (res.status >= 300) {
|
|
42
60
|
return
|
|
@@ -80,4 +98,25 @@ describe("/api/global/email", () => {
|
|
|
80
98
|
it("should be able to send a password recovery email", async () => {
|
|
81
99
|
await sendRealEmail(EmailTemplatePurpose.PASSWORD_RECOVERY)
|
|
82
100
|
})
|
|
101
|
+
|
|
102
|
+
it("should be able to send an email with attachments", async () => {
|
|
103
|
+
let bucket = "testbucket"
|
|
104
|
+
let filename = "test.txt"
|
|
105
|
+
await objectStore.upload({
|
|
106
|
+
bucket,
|
|
107
|
+
filename,
|
|
108
|
+
body: Buffer.from("test data"),
|
|
109
|
+
})
|
|
110
|
+
let presignedUrl = await objectStore.getPresignedUrl(
|
|
111
|
+
bucket,
|
|
112
|
+
filename,
|
|
113
|
+
60000
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
let attachmentObject = {
|
|
117
|
+
url: presignedUrl,
|
|
118
|
+
filename,
|
|
119
|
+
}
|
|
120
|
+
await sendRealEmail(EmailTemplatePurpose.WELCOME, [attachmentObject])
|
|
121
|
+
})
|
|
83
122
|
})
|
package/src/tests/api/email.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EmailAttachment } from "@budibase/types"
|
|
1
2
|
import TestConfiguration from "../TestConfiguration"
|
|
2
3
|
import { TestAPI } from "./base"
|
|
3
4
|
|
|
@@ -6,11 +7,12 @@ export class EmailAPI extends TestAPI {
|
|
|
6
7
|
super(config)
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
sendEmail = (purpose: string) => {
|
|
10
|
+
sendEmail = (purpose: string, attachments?: EmailAttachment[]) => {
|
|
10
11
|
return this.request
|
|
11
12
|
.post(`/api/global/email/send`)
|
|
12
13
|
.send({
|
|
13
14
|
email: "test@example.com",
|
|
15
|
+
attachments,
|
|
14
16
|
purpose,
|
|
15
17
|
tenantId: this.config.getTenantId(),
|
|
16
18
|
userId: this.config.user?._id!,
|
package/src/tests/jestEnv.ts
CHANGED
|
@@ -4,8 +4,8 @@ process.env.JWT_SECRET = "test-jwtsecret"
|
|
|
4
4
|
process.env.LOG_LEVEL = process.env.LOG_LEVEL || "error"
|
|
5
5
|
process.env.MULTI_TENANCY = "1"
|
|
6
6
|
process.env.MINIO_URL = "http://localhost"
|
|
7
|
-
process.env.MINIO_ACCESS_KEY = "
|
|
8
|
-
process.env.MINIO_SECRET_KEY = "
|
|
7
|
+
process.env.MINIO_ACCESS_KEY = "budibase"
|
|
8
|
+
process.env.MINIO_SECRET_KEY = "budibase"
|
|
9
9
|
process.env.PLATFORM_URL = "http://localhost:10000"
|
|
10
10
|
process.env.INTERNAL_API_KEY = "tet"
|
|
11
11
|
process.env.DISABLE_ACCOUNT_PORTAL = "0"
|
|
@@ -62,8 +62,8 @@ export function smtpEthereal(): SMTPConfig {
|
|
|
62
62
|
from: "testfrom@example.com",
|
|
63
63
|
secure: false,
|
|
64
64
|
auth: {
|
|
65
|
-
user: "
|
|
66
|
-
pass: "
|
|
65
|
+
user: "mortimer.leuschke@ethereal.email",
|
|
66
|
+
pass: "5hSjsPbzRv7gEUsfzx",
|
|
67
67
|
},
|
|
68
68
|
connectionTimeout: 1000, // must be less than the jest default of 5000
|
|
69
69
|
},
|
package/src/utilities/email.ts
CHANGED
|
@@ -4,8 +4,10 @@ import { getTemplateByPurpose, EmailTemplates } from "../constants/templates"
|
|
|
4
4
|
import { getSettingsTemplateContext } from "./templates"
|
|
5
5
|
import { processString } from "@budibase/string-templates"
|
|
6
6
|
import { User, SendEmailOpts, SMTPInnerConfig } from "@budibase/types"
|
|
7
|
-
import { configs, cache } from "@budibase/backend-core"
|
|
7
|
+
import { configs, cache, objectStore } from "@budibase/backend-core"
|
|
8
8
|
import ical from "ical-generator"
|
|
9
|
+
import fetch from "node-fetch"
|
|
10
|
+
import path from "path"
|
|
9
11
|
|
|
10
12
|
const nodemailer = require("nodemailer")
|
|
11
13
|
|
|
@@ -162,6 +164,42 @@ export async function sendEmail(
|
|
|
162
164
|
contents: opts?.contents,
|
|
163
165
|
}),
|
|
164
166
|
}
|
|
167
|
+
if (opts?.attachments) {
|
|
168
|
+
const attachments = await Promise.all(
|
|
169
|
+
opts.attachments?.map(async attachment => {
|
|
170
|
+
const isFullyFormedUrl =
|
|
171
|
+
attachment.url.startsWith("http://") ||
|
|
172
|
+
attachment.url.startsWith("https://")
|
|
173
|
+
if (isFullyFormedUrl) {
|
|
174
|
+
const response = await fetch(attachment.url)
|
|
175
|
+
if (!response.ok) {
|
|
176
|
+
throw new Error(`unexpected response ${response.statusText}`)
|
|
177
|
+
}
|
|
178
|
+
const fallbackFilename = path.basename(
|
|
179
|
+
new URL(attachment.url).pathname
|
|
180
|
+
)
|
|
181
|
+
return {
|
|
182
|
+
filename: attachment.filename || fallbackFilename,
|
|
183
|
+
content: response?.body,
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
const url = attachment.url
|
|
187
|
+
const result = objectStore.extractBucketAndPath(url)
|
|
188
|
+
if (result === null) {
|
|
189
|
+
throw new Error("Invalid signed URL")
|
|
190
|
+
}
|
|
191
|
+
const { bucket, path } = result
|
|
192
|
+
const readStream = await objectStore.getReadStream(bucket, path)
|
|
193
|
+
const fallbackFilename = path.split("/").pop() || ""
|
|
194
|
+
return {
|
|
195
|
+
filename: attachment.filename || fallbackFilename,
|
|
196
|
+
content: readStream,
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
)
|
|
201
|
+
message = { ...message, attachments }
|
|
202
|
+
}
|
|
165
203
|
|
|
166
204
|
message = {
|
|
167
205
|
...message,
|