@budibase/worker 1.4.16 → 1.4.18-alpha.0

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.
Files changed (34) hide show
  1. package/Dockerfile +2 -0
  2. package/package.json +6 -6
  3. package/scripts/dev/manage.js +1 -0
  4. package/src/api/controllers/global/auth.ts +12 -4
  5. package/src/api/controllers/global/email.js +4 -0
  6. package/src/api/controllers/global/license.ts +5 -0
  7. package/src/api/controllers/global/{self.js → self.ts} +38 -39
  8. package/src/api/controllers/global/{templates.js → templates.ts} +19 -17
  9. package/src/api/controllers/global/users.ts +36 -46
  10. package/src/api/controllers/system/accounts.ts +5 -5
  11. package/src/api/routes/global/auth.js +1 -1
  12. package/src/api/routes/global/configs.js +1 -1
  13. package/src/api/routes/global/email.js +10 -2
  14. package/src/api/routes/global/license.ts +1 -0
  15. package/src/api/routes/global/roles.js +1 -1
  16. package/src/api/routes/global/self.ts +18 -0
  17. package/src/api/routes/global/{templates.js → templates.ts} +8 -8
  18. package/src/api/routes/global/tests/users.spec.ts +32 -33
  19. package/src/api/routes/global/users.js +4 -11
  20. package/src/api/routes/global/workspaces.js +1 -1
  21. package/src/api/routes/index.ts +34 -0
  22. package/src/api/routes/system/environment.js +1 -1
  23. package/src/api/routes/system/status.js +1 -1
  24. package/src/api/routes/system/tenants.js +1 -1
  25. package/src/api/routes/system/tests/accounts.spec.ts +4 -4
  26. package/src/api/routes/validation/users.ts +10 -5
  27. package/src/db/index.js +1 -1
  28. package/src/migrations/functions/globalInfoSyncUsers.ts +1 -1
  29. package/src/sdk/index.ts +7 -2
  30. package/src/sdk/users/users.ts +22 -38
  31. package/src/tests/api/users.ts +10 -10
  32. package/src/utilities/email.js +3 -1
  33. package/src/api/routes/global/self.js +0 -18
  34. package/src/api/routes/index.js +0 -34
package/Dockerfile CHANGED
@@ -23,5 +23,7 @@ ENV NODE_ENV=production
23
23
  ENV CLUSTER_MODE=${CLUSTER_MODE}
24
24
  ENV SERVICE=worker-service
25
25
  ENV POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
26
+ ENV TENANT_FEATURE_FLAGS=*:LICENSING,*:USER_GROUPS
27
+ ENV ACCOUNT_PORTAL_URL=https://account.budibase.app
26
28
 
27
29
  CMD ["./docker_run.sh"]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/worker",
3
3
  "email": "hi@budibase.com",
4
- "version": "1.4.16",
4
+ "version": "1.4.18-alpha.0",
5
5
  "description": "Budibase background service",
6
6
  "main": "src/index.ts",
7
7
  "repository": {
@@ -36,10 +36,10 @@
36
36
  "author": "Budibase",
37
37
  "license": "GPL-3.0",
38
38
  "dependencies": {
39
- "@budibase/backend-core": "^1.4.16",
40
- "@budibase/pro": "1.4.15",
41
- "@budibase/string-templates": "^1.4.16",
42
- "@budibase/types": "^1.4.16",
39
+ "@budibase/backend-core": "1.4.18-alpha.0",
40
+ "@budibase/pro": "1.4.17",
41
+ "@budibase/string-templates": "1.4.18-alpha.0",
42
+ "@budibase/types": "1.4.18-alpha.0",
43
43
  "@koa/router": "8.0.8",
44
44
  "@sentry/node": "6.17.7",
45
45
  "@techpass/passport-openidconnect": "0.3.2",
@@ -104,5 +104,5 @@
104
104
  "./scripts/jestSetup.js"
105
105
  ]
106
106
  },
107
- "gitHead": "84ea807176efe98ed8fa1f5aa9b3f755723c0853"
107
+ "gitHead": "98b35601971af8bfc10bf6a252d3213c0bc06a4a"
108
108
  }
@@ -28,6 +28,7 @@ async function init() {
28
28
  APPS_URL: "http://localhost:4001",
29
29
  SERVICE: "worker-service",
30
30
  DEPLOYMENT_ENVIRONMENT: "development",
31
+ TENANT_FEATURE_FLAGS: "*:LICENSING,*:USER_GROUPS",
31
32
  }
32
33
  let envFile = ""
33
34
  Object.keys(envFileJson).forEach(key => {
@@ -8,7 +8,7 @@ const { checkResetPasswordCode } = require("../../../utilities/redis")
8
8
  const { getGlobalDB } = require("@budibase/backend-core/tenancy")
9
9
  const env = require("../../../environment")
10
10
  import { events, users as usersCore, context } from "@budibase/backend-core"
11
- import { users } from "../../../sdk"
11
+ import sdk from "../../../sdk"
12
12
  import { User } from "@budibase/types"
13
13
 
14
14
  export const googleCallbackUrl = async (config: any) => {
@@ -167,7 +167,11 @@ export const googlePreAuth = async (ctx: any, next: any) => {
167
167
  workspace: ctx.query.workspace,
168
168
  })
169
169
  let callbackUrl = await exports.googleCallbackUrl(config)
170
- const strategy = await google.strategyFactory(config, callbackUrl, users.save)
170
+ const strategy = await google.strategyFactory(
171
+ config,
172
+ callbackUrl,
173
+ sdk.users.save
174
+ )
171
175
 
172
176
  return passport.authenticate(strategy, {
173
177
  scope: ["profile", "email"],
@@ -184,7 +188,11 @@ export const googleAuth = async (ctx: any, next: any) => {
184
188
  workspace: ctx.query.workspace,
185
189
  })
186
190
  const callbackUrl = await exports.googleCallbackUrl(config)
187
- const strategy = await google.strategyFactory(config, callbackUrl, users.save)
191
+ const strategy = await google.strategyFactory(
192
+ config,
193
+ callbackUrl,
194
+ sdk.users.save
195
+ )
188
196
 
189
197
  return passport.authenticate(
190
198
  strategy,
@@ -214,7 +222,7 @@ export const oidcStrategyFactory = async (ctx: any, configId: any) => {
214
222
  chosenConfig,
215
223
  callbackUrl
216
224
  )
217
- return oidc.strategyFactory(enrichedConfig, users.save)
225
+ return oidc.strategyFactory(enrichedConfig, sdk.users.save)
218
226
  }
219
227
 
220
228
  /**
@@ -10,6 +10,8 @@ exports.sendEmail = async ctx => {
10
10
  contents,
11
11
  from,
12
12
  subject,
13
+ cc,
14
+ bcc,
13
15
  automation,
14
16
  } = ctx.request.body
15
17
  let user
@@ -23,6 +25,8 @@ exports.sendEmail = async ctx => {
23
25
  contents,
24
26
  from,
25
27
  subject,
28
+ cc,
29
+ bcc,
26
30
  automation,
27
31
  })
28
32
  ctx.body = {
@@ -24,6 +24,11 @@ export const getInfo = async (ctx: any) => {
24
24
  ctx.status = 200
25
25
  }
26
26
 
27
+ export const deleteInfo = async (ctx: any) => {
28
+ await licensing.deleteLicenseInfo()
29
+ ctx.status = 200
30
+ }
31
+
27
32
  export const getQuotaUsage = async (ctx: any) => {
28
33
  ctx.body = await quotas.getQuotaUsage()
29
34
  }
@@ -1,39 +1,37 @@
1
- const {
2
- getGlobalDB,
3
- getTenantId,
4
- isUserInAppTenant,
5
- } = require("@budibase/backend-core/tenancy")
6
- const { generateDevInfoID, SEPARATOR } = require("@budibase/backend-core/db")
7
- const { user: userCache } = require("@budibase/backend-core/cache")
8
- const {
9
- hash,
10
- platformLogout,
11
- getCookie,
12
- clearCookie,
13
- } = require("@budibase/backend-core/utils")
14
- const { encrypt } = require("@budibase/backend-core/encryption")
15
- const { newid } = require("@budibase/backend-core/utils")
16
- const { users } = require("../../../sdk")
17
- const { Cookies } = require("@budibase/backend-core/constants")
18
- const { events, featureFlags } = require("@budibase/backend-core")
19
- const env = require("../../../environment")
1
+ import sdk from "../../../sdk"
2
+ import {
3
+ events,
4
+ featureFlags,
5
+ tenancy,
6
+ constants,
7
+ db as dbCore,
8
+ utils,
9
+ cache,
10
+ encryption,
11
+ } from "@budibase/backend-core"
12
+ import env from "../../../environment"
13
+ import { groups } from "@budibase/pro"
14
+ const { hash, platformLogout, getCookie, clearCookie, newid } = utils
15
+ const { user: userCache } = cache
20
16
 
21
17
  function newTestApiKey() {
22
18
  return env.ENCRYPTED_TEST_PUBLIC_API_KEY
23
19
  }
24
20
 
25
21
  function newApiKey() {
26
- return encrypt(`${getTenantId()}${SEPARATOR}${newid()}`)
22
+ return encryption.encrypt(
23
+ `${tenancy.getTenantId()}${dbCore.SEPARATOR}${newid()}`
24
+ )
27
25
  }
28
26
 
29
- function cleanupDevInfo(info) {
27
+ function cleanupDevInfo(info: any) {
30
28
  // user doesn't need to aware of dev doc info
31
29
  delete info._id
32
30
  delete info._rev
33
31
  return info
34
32
  }
35
33
 
36
- exports.generateAPIKey = async ctx => {
34
+ export async function generateAPIKey(ctx: any) {
37
35
  let userId
38
36
  let apiKey
39
37
  if (env.isTest() && ctx.request.body.userId) {
@@ -44,8 +42,8 @@ exports.generateAPIKey = async ctx => {
44
42
  apiKey = newApiKey()
45
43
  }
46
44
 
47
- const db = getGlobalDB()
48
- const id = generateDevInfoID(userId)
45
+ const db = tenancy.getGlobalDB()
46
+ const id = dbCore.generateDevInfoID(userId)
49
47
  let devInfo
50
48
  try {
51
49
  devInfo = await db.get(id)
@@ -57,9 +55,9 @@ exports.generateAPIKey = async ctx => {
57
55
  ctx.body = cleanupDevInfo(devInfo)
58
56
  }
59
57
 
60
- exports.fetchAPIKey = async ctx => {
61
- const db = getGlobalDB()
62
- const id = generateDevInfoID(ctx.user._id)
58
+ export async function fetchAPIKey(ctx: any) {
59
+ const db = tenancy.getGlobalDB()
60
+ const id = dbCore.generateDevInfoID(ctx.user._id)
63
61
  let devInfo
64
62
  try {
65
63
  devInfo = await db.get(id)
@@ -74,20 +72,20 @@ exports.fetchAPIKey = async ctx => {
74
72
  ctx.body = cleanupDevInfo(devInfo)
75
73
  }
76
74
 
77
- const checkCurrentApp = ctx => {
78
- const appCookie = getCookie(ctx, Cookies.CurrentApp)
79
- if (appCookie && !isUserInAppTenant(appCookie.appId)) {
75
+ const checkCurrentApp = (ctx: any) => {
76
+ const appCookie = getCookie(ctx, constants.Cookies.CurrentApp)
77
+ if (appCookie && !tenancy.isUserInAppTenant(appCookie.appId)) {
80
78
  // there is a currentapp cookie from another tenant
81
79
  // remove the cookie as this is incompatible with the builder
82
80
  // due to builder and admin permissions being removed
83
- clearCookie(ctx, Cookies.CurrentApp)
81
+ clearCookie(ctx, constants.Cookies.CurrentApp)
84
82
  }
85
83
  }
86
84
 
87
85
  /**
88
86
  * Add the attributes that are session based to the current user.
89
87
  */
90
- const addSessionAttributesToUser = ctx => {
88
+ const addSessionAttributesToUser = (ctx: any) => {
91
89
  ctx.body.account = ctx.user.account
92
90
  ctx.body.license = ctx.user.license
93
91
  ctx.body.budibaseAccess = !!ctx.user.budibaseAccess
@@ -95,9 +93,9 @@ const addSessionAttributesToUser = ctx => {
95
93
  ctx.body.csrfToken = ctx.user.csrfToken
96
94
  }
97
95
 
98
- const sanitiseUserUpdate = ctx => {
96
+ const sanitiseUserUpdate = (ctx: any) => {
99
97
  const allowed = ["firstName", "lastName", "password", "forceResetPassword"]
100
- const resp = {}
98
+ const resp: { [key: string]: any } = {}
101
99
  for (let [key, value] of Object.entries(ctx.request.body)) {
102
100
  if (allowed.includes(key)) {
103
101
  resp[key] = value
@@ -106,7 +104,7 @@ const sanitiseUserUpdate = ctx => {
106
104
  return resp
107
105
  }
108
106
 
109
- exports.getSelf = async ctx => {
107
+ export async function getSelf(ctx: any) {
110
108
  if (!ctx.user) {
111
109
  ctx.throw(403, "User not logged in")
112
110
  }
@@ -118,17 +116,18 @@ exports.getSelf = async ctx => {
118
116
  checkCurrentApp(ctx)
119
117
 
120
118
  // get the main body of the user
121
- ctx.body = await users.getUser(userId)
119
+ const user = await sdk.users.getUser(userId)
120
+ ctx.body = await groups.enrichUserRolesFromGroups(user)
122
121
 
123
122
  // add the feature flags for this tenant
124
- const tenantId = getTenantId()
123
+ const tenantId = tenancy.getTenantId()
125
124
  ctx.body.featureFlags = featureFlags.getTenantFeatureFlags(tenantId)
126
125
 
127
126
  addSessionAttributesToUser(ctx)
128
127
  }
129
128
 
130
- exports.updateSelf = async ctx => {
131
- const db = getGlobalDB()
129
+ export async function updateSelf(ctx: any) {
130
+ const db = tenancy.getGlobalDB()
132
131
  const user = await db.get(ctx.user._id)
133
132
  let passwordChange = false
134
133
 
@@ -1,20 +1,19 @@
1
- const { generateTemplateID } = require("@budibase/backend-core/db")
2
- const {
1
+ import {
3
2
  TemplateMetadata,
4
3
  TemplateBindings,
5
4
  GLOBAL_OWNER,
6
- } = require("../../../constants")
7
- const { getTemplates } = require("../../../constants/templates")
8
- const { getGlobalDB } = require("@budibase/backend-core/tenancy")
5
+ } from "../../../constants"
6
+ import { getTemplates } from "../../../constants/templates"
7
+ import { tenancy, db as dbCore } from "@budibase/backend-core"
9
8
 
10
- exports.save = async ctx => {
11
- const db = getGlobalDB()
9
+ export async function save(ctx: any) {
10
+ const db = tenancy.getGlobalDB()
12
11
  let template = ctx.request.body
13
12
  if (!template.ownerId) {
14
13
  template.ownerId = GLOBAL_OWNER
15
14
  }
16
15
  if (!template._id) {
17
- template._id = generateTemplateID(template.ownerId)
16
+ template._id = dbCore.generateTemplateID(template.ownerId)
18
17
  }
19
18
 
20
19
  const response = await db.put(template)
@@ -24,9 +23,9 @@ exports.save = async ctx => {
24
23
  }
25
24
  }
26
25
 
27
- exports.definitions = async ctx => {
28
- const bindings = {}
29
- const info = {}
26
+ export async function definitions(ctx: any) {
27
+ const bindings: any = {}
28
+ const info: any = {}
30
29
  for (let template of TemplateMetadata.email) {
31
30
  bindings[template.purpose] = template.bindings
32
31
  info[template.purpose] = {
@@ -45,30 +44,33 @@ exports.definitions = async ctx => {
45
44
  }
46
45
  }
47
46
 
48
- exports.fetch = async ctx => {
47
+ export async function fetch(ctx: any) {
49
48
  ctx.body = await getTemplates()
50
49
  }
51
50
 
52
- exports.fetchByType = async ctx => {
51
+ export async function fetchByType(ctx: any) {
52
+ // @ts-ignore
53
53
  ctx.body = await getTemplates({
54
54
  type: ctx.params.type,
55
55
  })
56
56
  }
57
57
 
58
- exports.fetchByOwner = async ctx => {
58
+ export async function fetchByOwner(ctx: any) {
59
+ // @ts-ignore
59
60
  ctx.body = await getTemplates({
60
61
  ownerId: ctx.params.ownerId,
61
62
  })
62
63
  }
63
64
 
64
- exports.find = async ctx => {
65
+ export async function find(ctx: any) {
66
+ // @ts-ignore
65
67
  ctx.body = await getTemplates({
66
68
  id: ctx.params.id,
67
69
  })
68
70
  }
69
71
 
70
- exports.destroy = async ctx => {
71
- const db = getGlobalDB()
72
+ export async function destroy(ctx: any) {
73
+ const db = tenancy.getGlobalDB()
72
74
  await db.remove(ctx.params.id, ctx.params.rev)
73
75
  ctx.message = `Template ${ctx.params.id} deleted.`
74
76
  ctx.status = 200
@@ -1,8 +1,9 @@
1
1
  import { checkInviteCode } from "../../../utilities/redis"
2
- import { users } from "../../../sdk"
2
+ import sdk from "../../../sdk"
3
3
  import env from "../../../environment"
4
4
  import {
5
- BulkDeleteUsersRequest,
5
+ BulkUserRequest,
6
+ BulkUserResponse,
6
7
  CloudAccount,
7
8
  InviteUserRequest,
8
9
  InviteUsersRequest,
@@ -16,46 +17,48 @@ import {
16
17
  tenancy,
17
18
  } from "@budibase/backend-core"
18
19
  import { checkAnyUserExists } from "../../../utilities/users"
19
- import { groups as groupUtils } from "@budibase/pro"
20
20
 
21
21
  const MAX_USERS_UPLOAD_LIMIT = 1000
22
22
 
23
23
  export const save = async (ctx: any) => {
24
24
  try {
25
- ctx.body = await users.save(ctx.request.body)
25
+ ctx.body = await sdk.users.save(ctx.request.body)
26
26
  } catch (err: any) {
27
27
  ctx.throw(err.status || 400, err)
28
28
  }
29
29
  }
30
30
 
31
- export const bulkCreate = async (ctx: any) => {
32
- let { users: newUsersRequested, groups } = ctx.request.body
31
+ const bulkDelete = async (userIds: string[], currentUserId: string) => {
32
+ if (userIds?.indexOf(currentUserId) !== -1) {
33
+ throw new Error("Unable to delete self.")
34
+ }
35
+ return await sdk.users.bulkDelete(userIds)
36
+ }
33
37
 
34
- if (!env.SELF_HOSTED && newUsersRequested.length > MAX_USERS_UPLOAD_LIMIT) {
35
- ctx.throw(
36
- 400,
38
+ const bulkCreate = async (users: User[], groupIds: string[]) => {
39
+ if (!env.SELF_HOSTED && users.length > MAX_USERS_UPLOAD_LIMIT) {
40
+ throw new Error(
37
41
  "Max limit for upload is 1000 users. Please reduce file size and try again."
38
42
  )
39
43
  }
44
+ return await sdk.users.bulkCreate(users, groupIds)
45
+ }
40
46
 
41
- const db = tenancy.getGlobalDB()
42
- let groupsToSave: any[] = []
43
-
44
- if (groups.length) {
45
- for (const groupId of groups) {
46
- let oldGroup = await db.get(groupId)
47
- groupsToSave.push(oldGroup)
48
- }
49
- }
50
-
47
+ export const bulkUpdate = async (ctx: any) => {
48
+ const currentUserId = ctx.user._id
49
+ const input = ctx.request.body as BulkUserRequest
50
+ let created, deleted
51
51
  try {
52
- const response = await users.bulkCreate(newUsersRequested, groups)
53
- await groupUtils.bulkSaveGroupUsers(groupsToSave, response.successful)
54
-
55
- ctx.body = response
52
+ if (input.create) {
53
+ created = await bulkCreate(input.create.users, input.create.groups)
54
+ }
55
+ if (input.delete) {
56
+ deleted = await bulkDelete(input.delete.userIds, currentUserId)
57
+ }
56
58
  } catch (err: any) {
57
- ctx.throw(err.status || 400, err)
59
+ ctx.throw(err.status || 400, err?.message || err)
58
60
  }
61
+ ctx.body = { created, deleted } as BulkUserResponse
59
62
  }
60
63
 
61
64
  const parseBooleanParam = (param: any) => {
@@ -99,7 +102,7 @@ export const adminUser = async (ctx: any) => {
99
102
  // always bust checklist beforehand, if an error occurs but can proceed, don't get
100
103
  // stuck in a cycle
101
104
  await cache.bustCache(cache.CacheKeys.CHECKLIST)
102
- const finalUser = await users.save(user, {
105
+ const finalUser = await sdk.users.save(user, {
103
106
  hashPassword,
104
107
  requirePassword,
105
108
  })
@@ -121,7 +124,7 @@ export const adminUser = async (ctx: any) => {
121
124
  export const countByApp = async (ctx: any) => {
122
125
  const appId = ctx.params.appId
123
126
  try {
124
- ctx.body = await users.countUsersByApp(appId)
127
+ ctx.body = await sdk.users.countUsersByApp(appId)
125
128
  } catch (err: any) {
126
129
  ctx.throw(err.status || 400, err)
127
130
  }
@@ -133,28 +136,15 @@ export const destroy = async (ctx: any) => {
133
136
  ctx.throw(400, "Unable to delete self.")
134
137
  }
135
138
 
136
- await users.destroy(id, ctx.user)
139
+ await sdk.users.destroy(id, ctx.user)
137
140
 
138
141
  ctx.body = {
139
142
  message: `User ${id} deleted.`,
140
143
  }
141
144
  }
142
145
 
143
- export const bulkDelete = async (ctx: any) => {
144
- const { userIds } = ctx.request.body as BulkDeleteUsersRequest
145
- if (userIds?.indexOf(ctx.user._id) !== -1) {
146
- ctx.throw(400, "Unable to delete self.")
147
- }
148
-
149
- try {
150
- ctx.body = await users.bulkDelete(userIds)
151
- } catch (err) {
152
- ctx.throw(err)
153
- }
154
- }
155
-
156
146
  export const search = async (ctx: any) => {
157
- const paginated = await users.paginatedUsers(ctx.request.body)
147
+ const paginated = await sdk.users.paginatedUsers(ctx.request.body)
158
148
  // user hashed password shouldn't ever be returned
159
149
  for (let user of paginated.data) {
160
150
  if (user) {
@@ -166,7 +156,7 @@ export const search = async (ctx: any) => {
166
156
 
167
157
  // called internally by app server user fetch
168
158
  export const fetch = async (ctx: any) => {
169
- const all = await users.allUsers()
159
+ const all = await sdk.users.allUsers()
170
160
  // user hashed password shouldn't ever be returned
171
161
  for (let user of all) {
172
162
  if (user) {
@@ -178,7 +168,7 @@ export const fetch = async (ctx: any) => {
178
168
 
179
169
  // called internally by app server user find
180
170
  export const find = async (ctx: any) => {
181
- ctx.body = await users.getUser(ctx.params.id)
171
+ ctx.body = await sdk.users.getUser(ctx.params.id)
182
172
  }
183
173
 
184
174
  export const tenantUserLookup = async (ctx: any) => {
@@ -193,7 +183,7 @@ export const tenantUserLookup = async (ctx: any) => {
193
183
 
194
184
  export const invite = async (ctx: any) => {
195
185
  const request = ctx.request.body as InviteUserRequest
196
- const response = await users.invite([request])
186
+ const response = await sdk.users.invite([request])
197
187
 
198
188
  // explicitly throw for single user invite
199
189
  if (response.unsuccessful.length) {
@@ -212,7 +202,7 @@ export const invite = async (ctx: any) => {
212
202
 
213
203
  export const inviteMultiple = async (ctx: any) => {
214
204
  const request = ctx.request.body as InviteUsersRequest
215
- ctx.body = await users.invite(request)
205
+ ctx.body = await sdk.users.invite(request)
216
206
  }
217
207
 
218
208
  export const inviteAccept = async (ctx: any) => {
@@ -221,7 +211,7 @@ export const inviteAccept = async (ctx: any) => {
221
211
  // info is an extension of the user object that was stored by global
222
212
  const { email, info }: any = await checkInviteCode(inviteCode)
223
213
  ctx.body = await tenancy.doInTenant(info.tenantId, async () => {
224
- const saved = await users.save({
214
+ const saved = await sdk.users.save({
225
215
  firstName,
226
216
  lastName,
227
217
  password,
@@ -1,21 +1,21 @@
1
1
  import { Account, AccountMetadata } from "@budibase/types"
2
- import { accounts } from "../../../sdk"
2
+ import sdk from "../../../sdk"
3
3
 
4
4
  export const save = async (ctx: any) => {
5
5
  const account = ctx.request.body as Account
6
6
  let metadata: AccountMetadata = {
7
- _id: accounts.formatAccountMetadataId(account.accountId),
7
+ _id: sdk.accounts.formatAccountMetadataId(account.accountId),
8
8
  email: account.email,
9
9
  }
10
10
 
11
- metadata = await accounts.saveMetadata(metadata)
11
+ metadata = await sdk.accounts.saveMetadata(metadata)
12
12
 
13
13
  ctx.body = metadata
14
14
  ctx.status = 200
15
15
  }
16
16
 
17
17
  export const destroy = async (ctx: any) => {
18
- const accountId = accounts.formatAccountMetadataId(ctx.params.accountId)
19
- await accounts.destroyMetadata(accountId)
18
+ const accountId = sdk.accounts.formatAccountMetadataId(ctx.params.accountId)
19
+ await sdk.accounts.destroyMetadata(accountId)
20
20
  ctx.status = 204
21
21
  }
@@ -4,7 +4,7 @@ const { joiValidator } = require("@budibase/backend-core/auth")
4
4
  const Joi = require("joi")
5
5
  const { updateTenantId } = require("@budibase/backend-core/tenancy")
6
6
 
7
- const router = Router()
7
+ const router = new Router()
8
8
 
9
9
  function buildAuthValidation() {
10
10
  // prettier-ignore
@@ -5,7 +5,7 @@ const { adminOnly } = require("@budibase/backend-core/auth")
5
5
  const Joi = require("joi")
6
6
  const { Configs } = require("../../../constants")
7
7
 
8
- const router = Router()
8
+ const router = new Router()
9
9
 
10
10
  function smtpValidation() {
11
11
  // prettier-ignore
@@ -5,12 +5,20 @@ const { joiValidator } = require("@budibase/backend-core/auth")
5
5
  const { adminOnly } = require("@budibase/backend-core/auth")
6
6
  const Joi = require("joi")
7
7
 
8
- const router = Router()
8
+ const router = new Router()
9
9
 
10
10
  function buildEmailSendValidation() {
11
11
  // prettier-ignore
12
12
  return joiValidator.body(Joi.object({
13
- email: Joi.string().email(),
13
+ email: Joi.string().email({
14
+ multiple: true,
15
+ }),
16
+ cc: Joi.string().email({
17
+ multiple: true,
18
+ }).allow("", null),
19
+ bcc: Joi.string().email({
20
+ multiple: true,
21
+ }).allow("", null),
14
22
  purpose: Joi.string().valid(...Object.values(EmailTemplatePurpose)),
15
23
  workspaceId: Joi.string().allow("", null),
16
24
  from: Joi.string().allow("", null),
@@ -7,6 +7,7 @@ router
7
7
  .post("/api/global/license/activate", controller.activate)
8
8
  .post("/api/global/license/refresh", controller.refresh)
9
9
  .get("/api/global/license/info", controller.getInfo)
10
+ .delete("/api/global/license/info", controller.deleteInfo)
10
11
  .get("/api/global/license/usage", controller.getQuotaUsage)
11
12
 
12
13
  export = router
@@ -2,7 +2,7 @@ const Router = require("@koa/router")
2
2
  const controller = require("../../controllers/global/roles")
3
3
  const { builderOrAdmin } = require("@budibase/backend-core/auth")
4
4
 
5
- const router = Router()
5
+ const router = new Router()
6
6
 
7
7
  router
8
8
  .get("/api/global/roles", builderOrAdmin, controller.fetch)
@@ -0,0 +1,18 @@
1
+ import Router from "@koa/router"
2
+ import * as controller from "../../controllers/global/self"
3
+ import { auth } from "@budibase/backend-core"
4
+ import { users } from "../validation"
5
+
6
+ const router = new Router()
7
+
8
+ router
9
+ .post("/api/global/self/api_key", auth.builderOnly, controller.generateAPIKey)
10
+ .get("/api/global/self/api_key", auth.builderOnly, controller.fetchAPIKey)
11
+ .get("/api/global/self", controller.getSelf)
12
+ .post(
13
+ "/api/global/self",
14
+ users.buildUserSaveValidation(true),
15
+ controller.updateSelf
16
+ )
17
+
18
+ export default router as any
@@ -1,11 +1,11 @@
1
- const Router = require("@koa/router")
2
- const controller = require("../../controllers/global/templates")
3
- const { joiValidator } = require("@budibase/backend-core/auth")
4
- const Joi = require("joi")
5
- const { TemplatePurpose, TemplateTypes } = require("../../../constants")
6
- const { adminOnly } = require("@budibase/backend-core/auth")
1
+ import Router from "@koa/router"
2
+ import * as controller from "../../controllers/global/templates"
3
+ import { TemplatePurpose, TemplateTypes } from "../../../constants"
4
+ import { auth as authCore } from "@budibase/backend-core"
5
+ import Joi from "joi"
6
+ const { adminOnly, joiValidator } = authCore
7
7
 
8
- const router = Router()
8
+ const router = new Router()
9
9
 
10
10
  function buildTemplateSaveValidation() {
11
11
  // prettier-ignore
@@ -34,4 +34,4 @@ router
34
34
  .get("/api/global/template/:id", controller.find)
35
35
  .delete("/api/global/template/:id/:rev", adminOnly, controller.destroy)
36
36
 
37
- module.exports = router
37
+ export default router
@@ -97,16 +97,16 @@ describe("/api/global/users", () => {
97
97
  })
98
98
  })
99
99
 
100
- describe("bulkCreate", () => {
100
+ describe("bulk (create)", () => {
101
101
  it("should ignore users existing in the same tenant", async () => {
102
102
  const user = await config.createUser()
103
103
  jest.clearAllMocks()
104
104
 
105
105
  const response = await api.users.bulkCreateUsers([user])
106
106
 
107
- expect(response.successful.length).toBe(0)
108
- expect(response.unsuccessful.length).toBe(1)
109
- expect(response.unsuccessful[0].email).toBe(user.email)
107
+ expect(response.created?.successful.length).toBe(0)
108
+ expect(response.created?.unsuccessful.length).toBe(1)
109
+ expect(response.created?.unsuccessful[0].email).toBe(user.email)
110
110
  expect(events.user.created).toBeCalledTimes(0)
111
111
  })
112
112
 
@@ -117,9 +117,9 @@ describe("/api/global/users", () => {
117
117
  await tenancy.doInTenant(TENANT_1, async () => {
118
118
  const response = await api.users.bulkCreateUsers([user])
119
119
 
120
- expect(response.successful.length).toBe(0)
121
- expect(response.unsuccessful.length).toBe(1)
122
- expect(response.unsuccessful[0].email).toBe(user.email)
120
+ expect(response.created?.successful.length).toBe(0)
121
+ expect(response.created?.unsuccessful.length).toBe(1)
122
+ expect(response.created?.unsuccessful[0].email).toBe(user.email)
123
123
  expect(events.user.created).toBeCalledTimes(0)
124
124
  })
125
125
  })
@@ -132,24 +132,24 @@ describe("/api/global/users", () => {
132
132
 
133
133
  const response = await api.users.bulkCreateUsers([user])
134
134
 
135
- expect(response.successful.length).toBe(0)
136
- expect(response.unsuccessful.length).toBe(1)
137
- expect(response.unsuccessful[0].email).toBe(user.email)
135
+ expect(response.created?.successful.length).toBe(0)
136
+ expect(response.created?.unsuccessful.length).toBe(1)
137
+ expect(response.created?.unsuccessful[0].email).toBe(user.email)
138
138
  expect(events.user.created).toBeCalledTimes(0)
139
139
  })
140
140
 
141
- it("should be able to bulkCreate users", async () => {
141
+ it("should be able to bulk create users", async () => {
142
142
  const builder = structures.users.builderUser()
143
143
  const admin = structures.users.adminUser()
144
144
  const user = structures.users.user()
145
145
 
146
146
  const response = await api.users.bulkCreateUsers([builder, admin, user])
147
147
 
148
- expect(response.successful.length).toBe(3)
149
- expect(response.successful[0].email).toBe(builder.email)
150
- expect(response.successful[1].email).toBe(admin.email)
151
- expect(response.successful[2].email).toBe(user.email)
152
- expect(response.unsuccessful.length).toBe(0)
148
+ expect(response.created?.successful.length).toBe(3)
149
+ expect(response.created?.successful[0].email).toBe(builder.email)
150
+ expect(response.created?.successful[1].email).toBe(admin.email)
151
+ expect(response.created?.successful[2].email).toBe(user.email)
152
+ expect(response.created?.unsuccessful.length).toBe(0)
153
153
  expect(events.user.created).toBeCalledTimes(3)
154
154
  expect(events.user.permissionAdminAssigned).toBeCalledTimes(1)
155
155
  expect(events.user.permissionBuilderAssigned).toBeCalledTimes(2)
@@ -420,33 +420,30 @@ describe("/api/global/users", () => {
420
420
  })
421
421
  })
422
422
 
423
- describe("bulkDelete", () => {
424
- it("should not be able to bulkDelete current user", async () => {
423
+ describe("bulk (delete)", () => {
424
+ it("should not be able to bulk delete current user", async () => {
425
425
  const user = await config.defaultUser!
426
- const request = { userIds: [user._id!] }
427
426
 
428
- const response = await api.users.bulkDeleteUsers(request, 400)
427
+ const response = await api.users.bulkDeleteUsers([user._id!], 400)
429
428
 
430
- expect(response.body.message).toBe("Unable to delete self.")
429
+ expect(response.message).toBe("Unable to delete self.")
431
430
  expect(events.user.deleted).not.toBeCalled()
432
431
  })
433
432
 
434
- it("should not be able to bulkDelete account owner", async () => {
433
+ it("should not be able to bulk delete account owner", async () => {
435
434
  const user = await config.createUser()
436
435
  const account = structures.accounts.cloudAccount()
437
436
  account.budibaseUserId = user._id!
438
437
  mocks.accounts.getAccountByTenantId.mockReturnValue(account)
439
438
 
440
- const request = { userIds: [user._id!] }
441
-
442
- const response = await api.users.bulkDeleteUsers(request)
439
+ const response = await api.users.bulkDeleteUsers([user._id!])
443
440
 
444
- expect(response.body.successful.length).toBe(0)
445
- expect(response.body.unsuccessful.length).toBe(1)
446
- expect(response.body.unsuccessful[0].reason).toBe(
441
+ expect(response.deleted?.successful.length).toBe(0)
442
+ expect(response.deleted?.unsuccessful.length).toBe(1)
443
+ expect(response.deleted?.unsuccessful[0].reason).toBe(
447
444
  "Account holder cannot be deleted"
448
445
  )
449
- expect(response.body.unsuccessful[0]._id).toBe(user._id)
446
+ expect(response.deleted?.unsuccessful[0]._id).toBe(user._id)
450
447
  expect(events.user.deleted).not.toBeCalled()
451
448
  })
452
449
 
@@ -462,12 +459,14 @@ describe("/api/global/users", () => {
462
459
  admin,
463
460
  user,
464
461
  ])
465
- const request = { userIds: createdUsers.successful.map(u => u._id!) }
466
462
 
467
- const response = await api.users.bulkDeleteUsers(request)
463
+ const toDelete = createdUsers.created?.successful.map(
464
+ u => u._id!
465
+ ) as string[]
466
+ const response = await api.users.bulkDeleteUsers(toDelete)
468
467
 
469
- expect(response.body.successful.length).toBe(3)
470
- expect(response.body.unsuccessful.length).toBe(0)
468
+ expect(response.deleted?.successful.length).toBe(3)
469
+ expect(response.deleted?.unsuccessful.length).toBe(0)
471
470
  expect(events.user.deleted).toBeCalledTimes(3)
472
471
  expect(events.user.permissionAdminRemoved).toBeCalledTimes(1)
473
472
  expect(events.user.permissionBuilderRemoved).toBeCalledTimes(2)
@@ -8,7 +8,7 @@ const { users } = require("../validation")
8
8
  const selfController = require("../../controllers/global/self")
9
9
  const { builderOrAdmin } = require("@budibase/backend-core/auth")
10
10
 
11
- const router = Router()
11
+ const router = new Router()
12
12
 
13
13
  function buildAdminInitValidation() {
14
14
  return joiValidator.body(
@@ -56,16 +56,15 @@ router
56
56
  controller.save
57
57
  )
58
58
  .post(
59
- "/api/global/users/bulkCreate",
59
+ "/api/global/users/bulk",
60
60
  adminOnly,
61
- users.buildUserBulkSaveValidation(),
62
- controller.bulkCreate
61
+ users.buildUserBulkUserValidation(),
62
+ controller.bulkUpdate
63
63
  )
64
64
 
65
65
  .get("/api/global/users", builderOrAdmin, controller.fetch)
66
66
  .post("/api/global/users/search", builderOrAdmin, controller.search)
67
67
  .delete("/api/global/users/:id", adminOnly, controller.destroy)
68
- .post("/api/global/users/bulkDelete", adminOnly, controller.bulkDelete)
69
68
  .get("/api/global/users/count/:appId", builderOrAdmin, controller.countByApp)
70
69
  .get("/api/global/roles/:appId")
71
70
  .post(
@@ -74,12 +73,6 @@ router
74
73
  buildInviteValidation(),
75
74
  controller.invite
76
75
  )
77
- .post(
78
- "/api/global/users/invite",
79
- adminOnly,
80
- buildInviteValidation(),
81
- controller.invite
82
- )
83
76
  .post(
84
77
  "/api/global/users/multi/invite",
85
78
  adminOnly,
@@ -4,7 +4,7 @@ const { joiValidator } = require("@budibase/backend-core/auth")
4
4
  const { adminOnly } = require("@budibase/backend-core/auth")
5
5
  const Joi = require("joi")
6
6
 
7
- const router = Router()
7
+ const router = new Router()
8
8
 
9
9
  function buildWorkspaceSaveValidation() {
10
10
  // prettier-ignore
@@ -0,0 +1,34 @@
1
+ import { api } from "@budibase/pro"
2
+ import userRoutes from "./global/users"
3
+ import configRoutes from "./global/configs"
4
+ import workspaceRoutes from "./global/workspaces"
5
+ import templateRoutes from "./global/templates"
6
+ import emailRoutes from "./global/email"
7
+ import authRoutes from "./global/auth"
8
+ import roleRoutes from "./global/roles"
9
+ import environmentRoutes from "./system/environment"
10
+ import tenantsRoutes from "./system/tenants"
11
+ import statusRoutes from "./system/status"
12
+ import selfRoutes from "./global/self"
13
+ import licenseRoutes from "./global/license"
14
+ import migrationRoutes from "./system/migrations"
15
+ import accountRoutes from "./system/accounts"
16
+
17
+ let userGroupRoutes = api.groups
18
+ export const routes = [
19
+ configRoutes,
20
+ userRoutes,
21
+ workspaceRoutes,
22
+ authRoutes,
23
+ templateRoutes,
24
+ tenantsRoutes,
25
+ emailRoutes,
26
+ roleRoutes,
27
+ environmentRoutes,
28
+ statusRoutes,
29
+ selfRoutes,
30
+ licenseRoutes,
31
+ userGroupRoutes,
32
+ migrationRoutes,
33
+ accountRoutes,
34
+ ]
@@ -1,7 +1,7 @@
1
1
  const Router = require("@koa/router")
2
2
  const controller = require("../../controllers/system/environment")
3
3
 
4
- const router = Router()
4
+ const router = new Router()
5
5
 
6
6
  router.get("/api/system/environment", controller.fetch)
7
7
 
@@ -1,7 +1,7 @@
1
1
  const Router = require("@koa/router")
2
2
  const controller = require("../../controllers/system/status")
3
3
 
4
- const router = Router()
4
+ const router = new Router()
5
5
 
6
6
  router.get("/api/system/status", controller.fetch)
7
7
 
@@ -2,7 +2,7 @@ const Router = require("@koa/router")
2
2
  const controller = require("../../controllers/system/tenants")
3
3
  const { adminOnly } = require("@budibase/backend-core/auth")
4
4
 
5
- const router = Router()
5
+ const router = new Router()
6
6
 
7
7
  router
8
8
  .get("/api/system/tenants/:tenantId/exists", controller.exists)
@@ -1,4 +1,4 @@
1
- import { accounts } from "../../../../sdk"
1
+ import sdk from "../../../../sdk"
2
2
  import { TestConfiguration, structures, API } from "../../../../tests"
3
3
  import { v4 as uuid } from "uuid"
4
4
 
@@ -25,8 +25,8 @@ describe("accounts", () => {
25
25
 
26
26
  const response = await api.accounts.saveMetadata(account)
27
27
 
28
- const id = accounts.formatAccountMetadataId(account.accountId)
29
- const metadata = await accounts.getMetadata(id)
28
+ const id = sdk.accounts.formatAccountMetadataId(account.accountId)
29
+ const metadata = await sdk.accounts.getMetadata(id)
30
30
  expect(response).toStrictEqual(metadata)
31
31
  })
32
32
  })
@@ -38,7 +38,7 @@ describe("accounts", () => {
38
38
 
39
39
  await api.accounts.destroyMetadata(account.accountId)
40
40
 
41
- const deleted = await accounts.getMetadata(account.accountId)
41
+ const deleted = await sdk.accounts.getMetadata(account.accountId)
42
42
  expect(deleted).toBe(undefined)
43
43
  })
44
44
 
@@ -28,7 +28,7 @@ export const buildUserSaveValidation = (isSelf = false) => {
28
28
  return joiValidator.body(Joi.object(schema).required().unknown(true))
29
29
  }
30
30
 
31
- export const buildUserBulkSaveValidation = (isSelf = false) => {
31
+ export const buildUserBulkUserValidation = (isSelf = false) => {
32
32
  if (!isSelf) {
33
33
  schema = {
34
34
  ...schema,
@@ -36,10 +36,15 @@ export const buildUserBulkSaveValidation = (isSelf = false) => {
36
36
  _rev: Joi.string(),
37
37
  }
38
38
  }
39
- let bulkSaveSchema = {
40
- groups: Joi.array().optional(),
41
- users: Joi.array().items(Joi.object(schema).required().unknown(true)),
39
+ let bulkSchema = {
40
+ create: Joi.object({
41
+ groups: Joi.array().optional(),
42
+ users: Joi.array().items(Joi.object(schema).required().unknown(true)),
43
+ }),
44
+ delete: Joi.object({
45
+ userIds: Joi.array().items(Joi.string()),
46
+ }),
42
47
  }
43
48
 
44
- return joiValidator.body(Joi.object(bulkSaveSchema).required().unknown(true))
49
+ return joiValidator.body(Joi.object(bulkSchema).required().unknown(true))
45
50
  }
package/src/db/index.js CHANGED
@@ -3,7 +3,7 @@ const env = require("../environment")
3
3
 
4
4
  exports.init = () => {
5
5
  const dbConfig = {}
6
- if (env.isTest()) {
6
+ if (env.isTest() && !env.COUCH_DB_URL) {
7
7
  dbConfig.inMemory = true
8
8
  dbConfig.allDbs = true
9
9
  }
@@ -1,5 +1,5 @@
1
1
  import { User } from "@budibase/types"
2
- import * as sdk from "../../sdk"
2
+ import sdk from "../../sdk"
3
3
 
4
4
  /**
5
5
  * Date:
package/src/sdk/index.ts CHANGED
@@ -1,2 +1,7 @@
1
- export * as users from "./users"
2
- export * as accounts from "./accounts"
1
+ import * as users from "./users"
2
+ import * as accounts from "./accounts"
3
+
4
+ export default {
5
+ users,
6
+ accounts,
7
+ }
@@ -19,9 +19,7 @@ import {
19
19
  import {
20
20
  AccountMetadata,
21
21
  AllDocsResponse,
22
- BulkCreateUsersResponse,
23
- BulkDeleteUsersResponse,
24
- BulkDocsResponse,
22
+ BulkUserResponse,
25
23
  CloudAccount,
26
24
  CreateUserResponse,
27
25
  InviteUsersRequest,
@@ -31,9 +29,9 @@ import {
31
29
  RowResponse,
32
30
  User,
33
31
  } from "@budibase/types"
34
- import { groups as groupUtils } from "@budibase/pro"
35
32
  import { sendEmail } from "../../utilities/email"
36
33
  import { EmailTemplatePurpose } from "../../constants"
34
+ import { groups as groupsSdk } from "@budibase/pro"
37
35
 
38
36
  const PAGE_LIMIT = 8
39
37
 
@@ -349,8 +347,7 @@ const searchExistingEmails = async (emails: string[]) => {
349
347
  export const bulkCreate = async (
350
348
  newUsersRequested: User[],
351
349
  groups: string[]
352
- ): Promise<BulkCreateUsersResponse> => {
353
- const db = tenancy.getGlobalDB()
350
+ ): Promise<BulkUserResponse["created"]> => {
354
351
  const tenantId = tenancy.getTenantId()
355
352
 
356
353
  let usersToSave: any[] = []
@@ -392,9 +389,9 @@ export const bulkCreate = async (
392
389
  })
393
390
 
394
391
  const usersToBulkSave = await Promise.all(usersToSave)
395
- await db.bulkDocs(usersToBulkSave)
392
+ await usersCore.bulkUpdateGlobalUsers(usersToBulkSave)
396
393
 
397
- // Post processing of bulk added users, i.e events and cache operations
394
+ // Post-processing of bulk added users, e.g. events and cache operations
398
395
  for (const user of usersToBulkSave) {
399
396
  // TODO: Refactor to bulk insert users into the info db
400
397
  // instead of relying on looping tenant creation
@@ -410,6 +407,16 @@ export const bulkCreate = async (
410
407
  }
411
408
  })
412
409
 
410
+ // now update the groups
411
+ if (Array.isArray(saved) && groups) {
412
+ const groupPromises = []
413
+ const createdUserIds = saved.map(user => user._id)
414
+ for (let groupId of groups) {
415
+ groupPromises.push(groupsSdk.addUsers(groupId, createdUserIds))
416
+ }
417
+ await Promise.all(groupPromises)
418
+ }
419
+
413
420
  return {
414
421
  successful: saved,
415
422
  unsuccessful,
@@ -438,10 +445,10 @@ const getAccountHolderFromUserIds = async (
438
445
 
439
446
  export const bulkDelete = async (
440
447
  userIds: string[]
441
- ): Promise<BulkDeleteUsersResponse> => {
448
+ ): Promise<BulkUserResponse["deleted"]> => {
442
449
  const db = tenancy.getGlobalDB()
443
450
 
444
- const response: BulkDeleteUsersResponse = {
451
+ const response: BulkUserResponse["deleted"] = {
445
452
  successful: [],
446
453
  unsuccessful: [],
447
454
  }
@@ -458,7 +465,6 @@ export const bulkDelete = async (
458
465
  })
459
466
  }
460
467
 
461
- let groupsToModify: any = {}
462
468
  // Get users and delete
463
469
  const allDocsResponse: AllDocsResponse<User> = await db.allDocs({
464
470
  include_docs: true,
@@ -466,33 +472,16 @@ export const bulkDelete = async (
466
472
  })
467
473
  const usersToDelete: User[] = allDocsResponse.rows.map(
468
474
  (user: RowResponse<User>) => {
469
- // if we find a user that has an associated group, add it to
470
- // an array so we can easily use allDocs on them later.
471
- // This prevents us having to re-loop over all the users
472
- if (user.doc.userGroups) {
473
- for (let groupId of user.doc.userGroups) {
474
- if (!Object.keys(groupsToModify).includes(groupId)) {
475
- groupsToModify[groupId] = [user.id]
476
- } else {
477
- groupsToModify[groupId] = [...groupsToModify[groupId], user.id]
478
- }
479
- }
480
- }
481
-
482
475
  return user.doc
483
476
  }
484
477
  )
485
478
 
486
479
  // Delete from DB
487
- const dbResponse: BulkDocsResponse = await db.bulkDocs(
488
- usersToDelete.map(user => ({
489
- ...user,
490
- _deleted: true,
491
- }))
492
- )
493
-
494
- // Deletion post processing
495
- await groupUtils.bulkDeleteGroupUsers(groupsToModify)
480
+ const toDelete = usersToDelete.map(user => ({
481
+ ...user,
482
+ _deleted: true,
483
+ }))
484
+ const dbResponse = await usersCore.bulkUpdateGlobalUsers(toDelete)
496
485
  for (let user of usersToDelete) {
497
486
  await bulkDeleteProcessing(user)
498
487
  }
@@ -526,7 +515,6 @@ export const destroy = async (id: string, currentUser: any) => {
526
515
  const db = tenancy.getGlobalDB()
527
516
  const dbUser = await db.get(id)
528
517
  const userId = dbUser._id as string
529
- let groups = dbUser.userGroups
530
518
 
531
519
  if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
532
520
  // root account holder can't be deleted from inside budibase
@@ -545,10 +533,6 @@ export const destroy = async (id: string, currentUser: any) => {
545
533
 
546
534
  await db.remove(userId, dbUser._rev)
547
535
 
548
- if (groups) {
549
- await groupUtils.deleteGroupUsers(groups, dbUser)
550
- }
551
-
552
536
  await eventHelpers.handleDeleteEvents(dbUser)
553
537
  await cache.user.invalidateUser(userId)
554
538
  await sessions.invalidateSessions(userId, { reason: "deletion" })
@@ -1,8 +1,6 @@
1
1
  import {
2
- BulkCreateUsersRequest,
3
- BulkCreateUsersResponse,
4
- BulkDeleteUsersRequest,
5
- BulkDeleteUsersResponse,
2
+ BulkUserResponse,
3
+ BulkUserRequest,
6
4
  InviteUsersRequest,
7
5
  User,
8
6
  } from "@budibase/types"
@@ -69,24 +67,26 @@ export class UserAPI {
69
67
  // BULK
70
68
 
71
69
  bulkCreateUsers = async (users: User[], groups: any[] = []) => {
72
- const body: BulkCreateUsersRequest = { users, groups }
70
+ const body: BulkUserRequest = { create: { users, groups } }
73
71
  const res = await this.request
74
- .post(`/api/global/users/bulkCreate`)
72
+ .post(`/api/global/users/bulk`)
75
73
  .send(body)
76
74
  .set(this.config.defaultHeaders())
77
75
  .expect("Content-Type", /json/)
78
76
  .expect(200)
79
77
 
80
- return res.body as BulkCreateUsersResponse
78
+ return res.body as BulkUserResponse
81
79
  }
82
80
 
83
- bulkDeleteUsers = (body: BulkDeleteUsersRequest, status?: number) => {
84
- return this.request
85
- .post(`/api/global/users/bulkDelete`)
81
+ bulkDeleteUsers = async (userIds: string[], status?: number) => {
82
+ const body: BulkUserRequest = { delete: { userIds } }
83
+ const res = await this.request
84
+ .post(`/api/global/users/bulk`)
86
85
  .send(body)
87
86
  .set(this.config.defaultHeaders())
88
87
  .expect("Content-Type", /json/)
89
88
  .expect(status ? status : 200)
89
+ return res.body as BulkUserResponse
90
90
  }
91
91
 
92
92
  // USER
@@ -174,7 +174,7 @@ exports.isEmailConfigured = async (workspaceId = null) => {
174
174
  exports.sendEmail = async (
175
175
  email,
176
176
  purpose,
177
- { workspaceId, user, from, contents, subject, info, automation } = {}
177
+ { workspaceId, user, from, contents, subject, info, cc, bcc, automation } = {}
178
178
  ) => {
179
179
  const db = getGlobalDB()
180
180
  let config = (await getSmtpConfiguration(db, workspaceId, automation)) || {}
@@ -197,6 +197,8 @@ exports.sendEmail = async (
197
197
  message = {
198
198
  ...message,
199
199
  to: email,
200
+ cc: cc,
201
+ bcc: bcc,
200
202
  }
201
203
 
202
204
  if (subject || config.subject) {
@@ -1,18 +0,0 @@
1
- const Router = require("@koa/router")
2
- const controller = require("../../controllers/global/self")
3
- const { builderOnly } = require("@budibase/backend-core/auth")
4
- const { users } = require("../validation")
5
-
6
- const router = Router()
7
-
8
- router
9
- .post("/api/global/self/api_key", builderOnly, controller.generateAPIKey)
10
- .get("/api/global/self/api_key", builderOnly, controller.fetchAPIKey)
11
- .get("/api/global/self", controller.getSelf)
12
- .post(
13
- "/api/global/self",
14
- users.buildUserSaveValidation(true),
15
- controller.updateSelf
16
- )
17
-
18
- module.exports = router
@@ -1,34 +0,0 @@
1
- const { api } = require("@budibase/pro")
2
- const userRoutes = require("./global/users")
3
- const configRoutes = require("./global/configs")
4
- const workspaceRoutes = require("./global/workspaces")
5
- const templateRoutes = require("./global/templates")
6
- const emailRoutes = require("./global/email")
7
- const authRoutes = require("./global/auth")
8
- const roleRoutes = require("./global/roles")
9
- const environmentRoutes = require("./system/environment")
10
- const tenantsRoutes = require("./system/tenants")
11
- const statusRoutes = require("./system/status")
12
- const selfRoutes = require("./global/self")
13
- const licenseRoutes = require("./global/license")
14
- const migrationRoutes = require("./system/migrations")
15
- const accountRoutes = require("./system/accounts")
16
-
17
- let userGroupRoutes = api.groups
18
- exports.routes = [
19
- configRoutes,
20
- userRoutes,
21
- workspaceRoutes,
22
- authRoutes,
23
- templateRoutes,
24
- tenantsRoutes,
25
- emailRoutes,
26
- roleRoutes,
27
- environmentRoutes,
28
- statusRoutes,
29
- selfRoutes,
30
- licenseRoutes,
31
- userGroupRoutes,
32
- migrationRoutes,
33
- accountRoutes,
34
- ]