@budibase/worker 1.2.58-alpha.2 → 1.2.58-alpha.5

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": "1.2.58-alpha.2",
4
+ "version": "1.2.58-alpha.5",
5
5
  "description": "Budibase background service",
6
6
  "main": "src/index.ts",
7
7
  "repository": {
@@ -35,10 +35,10 @@
35
35
  "author": "Budibase",
36
36
  "license": "GPL-3.0",
37
37
  "dependencies": {
38
- "@budibase/backend-core": "1.2.58-alpha.2",
39
- "@budibase/pro": "1.2.58-alpha.1",
40
- "@budibase/string-templates": "1.2.58-alpha.2",
41
- "@budibase/types": "1.2.58-alpha.2",
38
+ "@budibase/backend-core": "1.2.58-alpha.5",
39
+ "@budibase/pro": "1.2.58-alpha.4",
40
+ "@budibase/string-templates": "1.2.58-alpha.5",
41
+ "@budibase/types": "1.2.58-alpha.5",
42
42
  "@koa/router": "8.0.8",
43
43
  "@sentry/node": "6.17.7",
44
44
  "@techpass/passport-openidconnect": "0.3.2",
@@ -103,5 +103,5 @@
103
103
  "./scripts/jestSetup.js"
104
104
  ]
105
105
  },
106
- "gitHead": "675fdd4b88456e4a663f8e2efb148556e0a35aab"
106
+ "gitHead": "6de0352299284a56f8d1bdd351cd2447a5b4c286"
107
107
  }
@@ -1,9 +1,13 @@
1
- import { EmailTemplatePurpose } from "../../../constants"
2
1
  import { checkInviteCode } from "../../../utilities/redis"
3
- import { sendEmail } from "../../../utilities/email"
4
2
  import { users } from "../../../sdk"
5
3
  import env from "../../../environment"
6
- import { BulkDeleteUsersRequest, CloudAccount, User } from "@budibase/types"
4
+ import {
5
+ BulkDeleteUsersRequest,
6
+ CloudAccount,
7
+ InviteUserRequest,
8
+ InviteUsersRequest,
9
+ User,
10
+ } from "@budibase/types"
7
11
  import {
8
12
  accounts,
9
13
  cache,
@@ -191,58 +195,27 @@ export const tenantUserLookup = async (ctx: any) => {
191
195
  }
192
196
 
193
197
  export const invite = async (ctx: any) => {
194
- let { email, userInfo } = ctx.request.body
195
- const existing = await usersCore.getGlobalUserByEmail(email)
196
- if (existing) {
197
- ctx.throw(400, "Email address already in use.")
198
- }
199
- if (!userInfo) {
200
- userInfo = {}
201
- }
202
- userInfo.tenantId = tenancy.getTenantId()
203
- const opts: any = {
204
- subject: "{{ company }} platform invitation",
205
- info: userInfo,
198
+ const request = ctx.request.body as InviteUserRequest
199
+ const response = await users.invite([request])
200
+
201
+ // explicitly throw for single user invite
202
+ if (response.unsuccessful.length) {
203
+ const reason = response.unsuccessful[0].reason
204
+ if (reason === "Unavailable") {
205
+ ctx.throw(400, reason)
206
+ } else {
207
+ ctx.throw(500, reason)
208
+ }
206
209
  }
207
- await sendEmail(email, EmailTemplatePurpose.INVITATION, opts)
210
+
208
211
  ctx.body = {
209
212
  message: "Invitation has been sent.",
210
213
  }
211
- await events.user.invited()
212
214
  }
213
215
 
214
216
  export const inviteMultiple = async (ctx: any) => {
215
- let users = ctx.request.body
216
- let existing = false
217
- let existingEmail
218
- for (let user of users) {
219
- if (await usersCore.getGlobalUserByEmail(user.email)) {
220
- existing = true
221
- existingEmail = user.email
222
- break
223
- }
224
- }
225
-
226
- if (existing) {
227
- ctx.throw(400, `${existingEmail} already exists`)
228
- }
229
-
230
- for (let i = 0; i < users.length; i++) {
231
- let userInfo = users[i].userInfo
232
- if (!userInfo) {
233
- userInfo = {}
234
- }
235
- userInfo.tenantId = tenancy.getTenantId()
236
- const opts: any = {
237
- subject: "{{ company }} platform invitation",
238
- info: userInfo,
239
- }
240
- await sendEmail(users[i].email, EmailTemplatePurpose.INVITATION, opts)
241
- }
242
-
243
- ctx.body = {
244
- message: "Invitations have been sent.",
245
- }
217
+ const request = ctx.request.body as InviteUsersRequest
218
+ ctx.body = await users.invite(request)
246
219
  }
247
220
 
248
221
  export const inviteAccept = async (ctx: any) => {
@@ -1,3 +1,5 @@
1
+ import { InviteUsersResponse } from "@budibase/types"
2
+
1
3
  jest.mock("nodemailer")
2
4
  import {
3
5
  TestConfiguration,
@@ -27,7 +29,8 @@ describe("/api/global/users", () => {
27
29
 
28
30
  describe("invite", () => {
29
31
  it("should be able to generate an invitation", async () => {
30
- const { code, res } = await api.users.sendUserInvite(sendMailMock)
32
+ const email = structures.users.newEmail()
33
+ const { code, res } = await api.users.sendUserInvite(sendMailMock, email)
31
34
 
32
35
  expect(res.body).toEqual({ message: "Invitation has been sent." })
33
36
  expect(sendMailMock).toHaveBeenCalled()
@@ -35,13 +38,27 @@ describe("/api/global/users", () => {
35
38
  expect(events.user.invited).toBeCalledTimes(1)
36
39
  })
37
40
 
41
+ it("should not be able to generate an invitation for existing user", async () => {
42
+ const { code, res } = await api.users.sendUserInvite(
43
+ sendMailMock,
44
+ config.defaultUser!.email,
45
+ 400
46
+ )
47
+
48
+ expect(res.body.message).toBe("Unavailable")
49
+ expect(sendMailMock).toHaveBeenCalledTimes(0)
50
+ expect(code).toBeUndefined()
51
+ expect(events.user.invited).toBeCalledTimes(0)
52
+ })
53
+
38
54
  it("should be able to create new user from invite", async () => {
39
- const { code } = await api.users.sendUserInvite(sendMailMock)
55
+ const email = structures.users.newEmail()
56
+ const { code } = await api.users.sendUserInvite(sendMailMock, email)
40
57
 
41
58
  const res = await api.users.acceptInvite(code)
42
59
 
43
60
  expect(res.body._id).toBeDefined()
44
- const user = await config.getUser("invite@test.com")
61
+ const user = await config.getUser(email)
45
62
  expect(user).toBeDefined()
46
63
  expect(user._id).toEqual(res.body._id)
47
64
  expect(events.user.inviteAccepted).toBeCalledTimes(1)
@@ -49,6 +66,37 @@ describe("/api/global/users", () => {
49
66
  })
50
67
  })
51
68
 
69
+ describe("inviteMultiple", () => {
70
+ it("should be able to generate an invitation", async () => {
71
+ const newUserInvite = () => ({
72
+ email: structures.users.newEmail(),
73
+ userInfo: {},
74
+ })
75
+ const request = [newUserInvite(), newUserInvite()]
76
+
77
+ const res = await api.users.sendMultiUserInvite(request)
78
+
79
+ const body = res.body as InviteUsersResponse
80
+ expect(body.successful.length).toBe(2)
81
+ expect(body.unsuccessful.length).toBe(0)
82
+ expect(sendMailMock).toHaveBeenCalledTimes(2)
83
+ expect(events.user.invited).toBeCalledTimes(2)
84
+ })
85
+
86
+ it("should not be able to generate an invitation for existing user", async () => {
87
+ const request = [{ email: config.defaultUser!.email, userInfo: {} }]
88
+
89
+ const res = await api.users.sendMultiUserInvite(request)
90
+
91
+ const body = res.body as InviteUsersResponse
92
+ expect(body.successful.length).toBe(0)
93
+ expect(body.unsuccessful.length).toBe(1)
94
+ expect(body.unsuccessful[0].reason).toBe("Unavailable")
95
+ expect(sendMailMock).toHaveBeenCalledTimes(0)
96
+ expect(events.user.invited).toBeCalledTimes(0)
97
+ })
98
+ })
99
+
52
100
  describe("bulkCreate", () => {
53
101
  it("should ignore users existing in the same tenant", async () => {
54
102
  const user = await config.createUser()
@@ -16,12 +16,12 @@ import {
16
16
  migrations,
17
17
  StaticDatabases,
18
18
  ViewName,
19
+ events,
19
20
  } from "@budibase/backend-core"
20
21
  import {
21
22
  MigrationType,
22
23
  PlatformUserByEmail,
23
24
  User,
24
- Account,
25
25
  BulkCreateUsersResponse,
26
26
  CreateUserResponse,
27
27
  BulkDeleteUsersResponse,
@@ -30,8 +30,12 @@ import {
30
30
  RowResponse,
31
31
  BulkDocsResponse,
32
32
  AccountMetadata,
33
+ InviteUsersRequest,
34
+ InviteUsersResponse,
33
35
  } from "@budibase/types"
34
36
  import { groups as groupUtils } from "@budibase/pro"
37
+ import { sendEmail } from "../../utilities/email"
38
+ import { EmailTemplatePurpose } from "../../constants"
35
39
 
36
40
  const PAGE_LIMIT = 8
37
41
 
@@ -551,3 +555,53 @@ const bulkDeleteProcessing = async (dbUser: User) => {
551
555
  // let server know to sync user
552
556
  await apps.syncUserInApps(userId)
553
557
  }
558
+
559
+ export const invite = async (
560
+ users: InviteUsersRequest
561
+ ): Promise<InviteUsersResponse> => {
562
+ const response: InviteUsersResponse = {
563
+ successful: [],
564
+ unsuccessful: [],
565
+ }
566
+
567
+ const matchedEmails = await searchExistingEmails(users.map(u => u.email))
568
+ const newUsers = []
569
+
570
+ // separate duplicates from new users
571
+ for (let user of users) {
572
+ if (matchedEmails.includes(user.email)) {
573
+ response.unsuccessful.push({ email: user.email, reason: "Unavailable" })
574
+ } else {
575
+ newUsers.push(user)
576
+ }
577
+ }
578
+ // overwrite users with new only
579
+ users = newUsers
580
+
581
+ // send the emails for new users
582
+ const tenantId = tenancy.getTenantId()
583
+ for (let user of users) {
584
+ try {
585
+ let userInfo = user.userInfo
586
+ if (!userInfo) {
587
+ userInfo = {}
588
+ }
589
+ userInfo.tenantId = tenantId
590
+ const opts: any = {
591
+ subject: "{{ company }} platform invitation",
592
+ info: userInfo,
593
+ }
594
+ await sendEmail(user.email, EmailTemplatePurpose.INVITATION, opts)
595
+ response.successful.push({ email: user.email })
596
+ await events.user.invited()
597
+ } catch (e) {
598
+ console.error(`Failed to send email invitation email=${user.email}`, e)
599
+ response.unsuccessful.push({
600
+ email: user.email,
601
+ reason: "Failed to send email",
602
+ })
603
+ }
604
+ }
605
+
606
+ return response
607
+ }
@@ -3,6 +3,7 @@ import {
3
3
  BulkCreateUsersResponse,
4
4
  BulkDeleteUsersRequest,
5
5
  CreateUserResponse,
6
+ InviteUsersRequest,
6
7
  User,
7
8
  UserDetails,
8
9
  } from "@budibase/types"
@@ -19,17 +20,21 @@ export class UserAPI {
19
20
 
20
21
  // INVITE
21
22
 
22
- sendUserInvite = async (sendMailMock: any) => {
23
+ sendUserInvite = async (sendMailMock: any, email: string, status = 200) => {
23
24
  await this.config.saveSmtpConfig()
24
25
  await this.config.saveSettingsConfig()
25
26
  const res = await this.request
26
27
  .post(`/api/global/users/invite`)
27
28
  .send({
28
- email: "invite@test.com",
29
+ email,
29
30
  })
30
31
  .set(this.config.defaultHeaders())
31
32
  .expect("Content-Type", /json/)
32
- .expect(200)
33
+ .expect(status)
34
+
35
+ if (status !== 200) {
36
+ return { code: undefined, res }
37
+ }
33
38
 
34
39
  const emailCall = sendMailMock.mock.calls[0][0]
35
40
  // after this URL there should be a code
@@ -51,6 +56,17 @@ export class UserAPI {
51
56
  .expect(200)
52
57
  }
53
58
 
59
+ sendMultiUserInvite = async (request: InviteUsersRequest, status = 200) => {
60
+ await this.config.saveSmtpConfig()
61
+ await this.config.saveSettingsConfig()
62
+ return this.request
63
+ .post(`/api/global/users/multi/invite`)
64
+ .send(request)
65
+ .set(this.config.defaultHeaders())
66
+ .expect("Content-Type", /json/)
67
+ .expect(status)
68
+ }
69
+
54
70
  // BULK
55
71
 
56
72
  bulkCreateUsers = async (users: User[], groups: any[] = []) => {