@edgedev/firebase 2.2.81 → 2.2.83

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 (3) hide show
  1. package/package.json +1 -1
  2. package/src/cms.js +284 -32
  3. package/src/edgeFirebase.js +124 -124
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edgedev/firebase",
3
- "version": "2.2.81",
3
+ "version": "2.2.83",
4
4
  "description": "Vue 3 / Nuxt 3 Plugin or Nuxt 3 plugin for firebase authentication and firestore.",
5
5
  "main": "index.ts",
6
6
  "scripts": {
package/src/cms.js CHANGED
@@ -235,16 +235,76 @@ const normalizeDomain = (value) => {
235
235
  }
236
236
  }
237
237
  normalized = normalized.split('/')[0] || ''
238
+ if (normalized.startsWith('[')) {
239
+ const closingIndex = normalized.indexOf(']')
240
+ if (closingIndex !== -1)
241
+ normalized = normalized.slice(0, closingIndex + 1)
242
+ }
238
243
  if (normalized.includes(':') && !normalized.startsWith('[')) {
239
244
  normalized = normalized.split(':')[0] || ''
240
245
  }
241
246
  return normalized.replace(/\.+$/g, '')
242
247
  }
243
248
 
249
+ const stripIpv6Brackets = (value) => {
250
+ const text = String(value || '').trim()
251
+ if (text.startsWith('[') && text.endsWith(']'))
252
+ return text.slice(1, -1)
253
+ return text
254
+ }
255
+
256
+ const isIpv4Address = (value) => {
257
+ const parts = String(value || '').split('.')
258
+ if (parts.length !== 4)
259
+ return false
260
+ return parts.every((part) => {
261
+ if (!/^\d{1,3}$/.test(part))
262
+ return false
263
+ const num = Number(part)
264
+ return num >= 0 && num <= 255
265
+ })
266
+ }
267
+
268
+ const isIpv6Address = (value) => {
269
+ const normalized = String(value || '').toLowerCase()
270
+ if (!normalized.includes(':'))
271
+ return false
272
+ return /^[0-9a-f:]+$/.test(normalized)
273
+ }
274
+
275
+ const isIpAddress = (value) => {
276
+ if (!value)
277
+ return false
278
+ const normalized = stripIpv6Brackets(value)
279
+ return isIpv4Address(normalized) || isIpv6Address(normalized)
280
+ }
281
+
282
+ const getCloudflareApexDomain = (domain) => {
283
+ if (!domain)
284
+ return ''
285
+ if (domain.startsWith('www.'))
286
+ return domain.slice(4)
287
+ return domain
288
+ }
289
+
290
+ const shouldDisplayDomainDnsRecords = (domain) => {
291
+ const normalizedDomain = normalizeDomain(domain)
292
+ const apexDomain = getCloudflareApexDomain(normalizedDomain)
293
+ if (!apexDomain)
294
+ return false
295
+ if (apexDomain === 'localhost' || apexDomain.endsWith('.localhost'))
296
+ return false
297
+ if (isIpAddress(apexDomain))
298
+ return false
299
+ if (apexDomain.endsWith('.dev'))
300
+ return false
301
+ return true
302
+ }
303
+
244
304
  const shouldSyncCloudflareDomain = (domain) => {
245
305
  if (!domain)
246
306
  return false
247
- if (domain.includes('localhost'))
307
+ if (!shouldDisplayDomainDnsRecords(domain))
248
308
  return false
249
309
  if (CLOUDFLARE_PAGES_PROJECT) {
250
310
  const pagesDomain = `${CLOUDFLARE_PAGES_PROJECT}.pages.dev`
@@ -262,6 +322,44 @@ const getCloudflarePagesDomain = (domain) => {
262
322
  return `www.${domain}`
263
323
  }
264
324
 
325
+ const getCloudflarePagesTarget = () => {
326
+ if (!CLOUDFLARE_PAGES_PROJECT)
327
+ return ''
328
+ return `${CLOUDFLARE_PAGES_PROJECT}.pages.dev`
329
+ }
330
+
331
+ const buildDomainDnsPayload = (domain, pagesTarget = '') => {
332
+ const normalizedDomain = normalizeDomain(domain)
333
+ const apexDomain = getCloudflareApexDomain(normalizedDomain)
334
+ const wwwDomain = getCloudflarePagesDomain(apexDomain)
335
+ const target = pagesTarget || getCloudflarePagesTarget()
336
+ const dnsEligible = shouldDisplayDomainDnsRecords(apexDomain)
337
+
338
+ return {
339
+ domain: normalizedDomain,
340
+ apexDomain,
341
+ wwwDomain,
342
+ dnsEligible,
343
+ dnsRecords: {
344
+ target,
345
+ www: {
346
+ type: 'CNAME',
347
+ name: 'www',
348
+ host: wwwDomain,
349
+ value: target,
350
+ enabled: dnsEligible && !!target,
351
+ },
352
+ apex: {
353
+ type: 'CNAME',
354
+ name: '@',
355
+ host: apexDomain,
356
+ value: target,
357
+ enabled: dnsEligible && !!target,
358
+ },
359
+ },
360
+ }
361
+ }
362
+
265
363
  const isCloudflareDomainAlreadyExistsError = (status, errors = [], message = '') => {
266
364
  if (status === 409)
267
365
  return true
@@ -1839,12 +1937,18 @@ const buildBlockAiPrompt = ({
1839
1937
  ].join('\n')
1840
1938
  }
1841
1939
 
1940
+ const assertCallableUser = (request) => {
1941
+ if (!request?.auth?.uid)
1942
+ throw new HttpsError('unauthenticated', 'Authentication required.')
1943
+ if (request?.data?.uid !== request.auth.uid)
1944
+ throw new HttpsError('permission-denied', 'UID mismatch.')
1945
+ }
1946
+
1842
1947
  exports.updateSeoFromAi = onCall({ timeoutSeconds: 180 }, async (request) => {
1948
+ assertCallableUser(request)
1843
1949
  const data = request.data || {}
1844
1950
  const auth = request.auth
1845
- const { orgId, siteId, pageId, uid } = data
1846
- if (!auth?.uid || auth.uid !== uid)
1847
- throw new HttpsError('permission-denied', 'Unauthorized')
1951
+ const { orgId, siteId, pageId } = data
1848
1952
  if (!orgId || !siteId || !pageId)
1849
1953
  throw new HttpsError('invalid-argument', 'Missing orgId, siteId, or pageId')
1850
1954
  const allowed = await permissionCheck(auth.uid, 'write', `organizations/${orgId}/sites/${siteId}/pages`)
@@ -1910,25 +2014,72 @@ exports.updateSeoFromAi = onCall({ timeoutSeconds: 180 }, async (request) => {
1910
2014
  })
1911
2015
 
1912
2016
  exports.getCloudflarePagesProject = onCall(async (request) => {
1913
- if (!request?.auth) {
1914
- throw new HttpsError('unauthenticated', 'Authentication required.')
1915
- }
2017
+ assertCallableUser(request)
2018
+ const data = request.data || {}
2019
+ const orgId = String(data.orgId || '').trim()
2020
+ const siteId = String(data.siteId || '').trim()
2021
+ const rawDomains = Array.isArray(data.domains) ? data.domains : []
2022
+ const normalizedDomains = Array.from(new Set(rawDomains.map(normalizeDomain).filter(Boolean)))
2023
+ const pagesTarget = getCloudflarePagesTarget()
1916
2024
 
1917
- if (!CLOUDFLARE_PAGES_PROJECT) {
2025
+ if (!CLOUDFLARE_PAGES_PROJECT)
1918
2026
  logger.warn('CLOUDFLARE_PAGES_PROJECT is not set.')
1919
- return { project: '' }
2027
+
2028
+ const domainRegistry = {}
2029
+ if (orgId && siteId && normalizedDomains.length) {
2030
+ const allowed = await permissionCheck(request.auth.uid, 'read', `organizations/${orgId}/sites`)
2031
+ if (!allowed)
2032
+ throw new HttpsError('permission-denied', 'Not allowed to read site settings')
2033
+
2034
+ await Promise.all(normalizedDomains.map(async (domain) => {
2035
+ const registryRef = db.collection(DOMAIN_REGISTRY_COLLECTION).doc(domain)
2036
+ const registrySnap = await registryRef.get()
2037
+ const fallback = buildDomainDnsPayload(domain, pagesTarget)
2038
+ if (!registrySnap.exists) {
2039
+ domainRegistry[domain] = {
2040
+ ...fallback,
2041
+ apexAttempted: false,
2042
+ apexAdded: false,
2043
+ apexError: '',
2044
+ dnsGuidance: fallback.dnsEligible
2045
+ ? 'Add the www CNAME. Apex is unavailable; forward apex to www.'
2046
+ : 'DNS records are not shown for localhost, IP addresses, or .dev domains.',
2047
+ }
2048
+ return
2049
+ }
2050
+
2051
+ const value = registrySnap.data() || {}
2052
+ domainRegistry[domain] = {
2053
+ ...fallback,
2054
+ ...value,
2055
+ dnsRecords: {
2056
+ ...fallback.dnsRecords,
2057
+ ...(value.dnsRecords || {}),
2058
+ www: {
2059
+ ...fallback.dnsRecords.www,
2060
+ ...(value?.dnsRecords?.www || {}),
2061
+ },
2062
+ apex: {
2063
+ ...fallback.dnsRecords.apex,
2064
+ ...(value?.dnsRecords?.apex || {}),
2065
+ },
2066
+ },
2067
+ }
2068
+ }))
1920
2069
  }
1921
2070
 
1922
- return { project: CLOUDFLARE_PAGES_PROJECT }
2071
+ return {
2072
+ project: CLOUDFLARE_PAGES_PROJECT || '',
2073
+ pagesDomain: pagesTarget,
2074
+ domainRegistry,
2075
+ }
1923
2076
  })
1924
2077
 
1925
2078
  exports.generateBlockFields = onCall({ timeoutSeconds: 180 }, async (request) => {
2079
+ assertCallableUser(request)
1926
2080
  const data = request.data || {}
1927
2081
  const auth = request.auth
1928
- const { orgId, uid, blockId, blockName, content, fields, currentValues, meta, instructions } = data
1929
-
1930
- if (!auth?.uid || auth.uid !== uid)
1931
- throw new HttpsError('permission-denied', 'Unauthorized')
2082
+ const { orgId, blockId, blockName, content, fields, currentValues, meta, instructions } = data
1932
2083
  if (!orgId || !blockId)
1933
2084
  throw new HttpsError('invalid-argument', 'Missing orgId or blockId')
1934
2085
  if (!Array.isArray(fields) || fields.length === 0)
@@ -2069,7 +2220,6 @@ exports.ensurePublishedSiteDomains = onDocumentWritten(
2069
2220
  const siteRef = change.after.ref
2070
2221
  const siteData = change.after.data() || {}
2071
2222
  const beforeData = change.before?.data?.() || {}
2072
- const domainErrorChanged = beforeData?.domainError !== siteData?.domainError
2073
2223
  const rawDomains = Array.isArray(siteData.domains) ? siteData.domains : []
2074
2224
  const normalizedDomains = Array.from(new Set(rawDomains.map(normalizeDomain).filter(Boolean)))
2075
2225
  const beforeRawDomains = Array.isArray(beforeData.domains) ? beforeData.domains : []
@@ -2139,41 +2289,143 @@ exports.ensurePublishedSiteDomains = onDocumentWritten(
2139
2289
  filteredDomains = normalizedDomains.filter(domain => !conflictSet.has(domain))
2140
2290
  }
2141
2291
 
2142
- const syncDomains = Array.from(new Set(
2143
- filteredDomains
2144
- .map(domain => getCloudflarePagesDomain(domain))
2145
- .filter(domain => shouldSyncCloudflareDomain(domain)),
2146
- ))
2292
+ const pagesTarget = getCloudflarePagesTarget()
2293
+ const registryStateByDomain = new Map()
2294
+ const syncPlanMap = new Map()
2295
+ for (const domain of filteredDomains) {
2296
+ const dnsPayload = buildDomainDnsPayload(domain, pagesTarget)
2297
+ registryStateByDomain.set(domain, {
2298
+ ...dnsPayload,
2299
+ wwwAdded: false,
2300
+ wwwError: '',
2301
+ apexAttempted: false,
2302
+ apexAdded: false,
2303
+ apexError: '',
2304
+ dnsGuidance: dnsPayload.dnsEligible
2305
+ ? 'Add the www CNAME record. Apex is unavailable; forward apex to www.'
2306
+ : 'DNS records are not shown for localhost, IP addresses, or .dev domains.',
2307
+ })
2308
+
2309
+ const apexDomain = dnsPayload.apexDomain
2310
+ if (!apexDomain)
2311
+ continue
2312
+ const existingPlan = syncPlanMap.get(apexDomain) || {
2313
+ apexDomain,
2314
+ wwwDomain: dnsPayload.wwwDomain,
2315
+ domains: new Set(),
2316
+ }
2317
+ existingPlan.domains.add(domain)
2318
+ syncPlanMap.set(apexDomain, existingPlan)
2319
+ }
2320
+
2321
+ const syncPlans = Array.from(syncPlanMap.values())
2322
+ .filter(plan => shouldSyncCloudflareDomain(plan.wwwDomain))
2323
+ .map(plan => ({ ...plan, domains: Array.from(plan.domains) }))
2324
+
2147
2325
  const removeDomains = Array.from(new Set(
2148
2326
  removedOwnedDomains
2149
- .map(domain => getCloudflarePagesDomain(domain))
2327
+ .flatMap((domain) => {
2328
+ const apexDomain = getCloudflareApexDomain(domain)
2329
+ const wwwDomain = getCloudflarePagesDomain(apexDomain)
2330
+ return [wwwDomain, apexDomain]
2331
+ })
2150
2332
  .filter(domain => shouldSyncCloudflareDomain(domain)),
2151
2333
  ))
2152
2334
  if (removeDomains.length) {
2153
2335
  await Promise.all(removeDomains.map(domain => removeCloudflarePagesDomain(domain, { orgId, siteId })))
2154
2336
  }
2155
- if (!syncDomains.length) {
2156
- if (!conflictDomains.length && siteData.domainError && !domainErrorChanged) {
2157
- await siteRef.set({ domainError: Firestore.FieldValue.delete() }, { merge: true })
2337
+
2338
+ const syncResults = await Promise.all(syncPlans.map(async (plan) => {
2339
+ const wwwResult = await addCloudflarePagesDomain(plan.wwwDomain, { orgId, siteId, variant: 'www' })
2340
+ let apexAttempted = false
2341
+ let apexResult = { ok: false, error: '' }
2342
+ if (shouldSyncCloudflareDomain(plan.apexDomain)) {
2343
+ apexAttempted = true
2344
+ apexResult = await addCloudflarePagesDomain(plan.apexDomain, { orgId, siteId, variant: 'apex' })
2345
+ }
2346
+ return {
2347
+ ...plan,
2348
+ apexAttempted,
2349
+ wwwResult,
2350
+ apexResult,
2351
+ }
2352
+ }))
2353
+
2354
+ for (const plan of syncResults) {
2355
+ const wwwAdded = !!plan.wwwResult?.ok
2356
+ const wwwError = wwwAdded ? '' : String(plan.wwwResult?.error || 'Failed to add www domain.')
2357
+ const apexAdded = !!plan.apexResult?.ok
2358
+ const apexError = apexAdded
2359
+ ? ''
2360
+ : (plan.apexAttempted ? String(plan.apexResult?.error || 'Failed to add apex domain.') : '')
2361
+
2362
+ for (const domain of plan.domains) {
2363
+ const current = registryStateByDomain.get(domain) || buildDomainDnsPayload(domain, pagesTarget)
2364
+ const dnsGuidance = !current.dnsEligible
2365
+ ? 'DNS records are not shown for localhost, IP addresses, or .dev domains.'
2366
+ : (apexAdded
2367
+ ? 'Apex and www were added to Cloudflare Pages. Add both DNS records if your provider requires manual setup.'
2368
+ : 'Add the www CNAME record. Apex is unavailable; forward apex to www.')
2369
+ const nextDnsRecords = {
2370
+ ...(current.dnsRecords || {}),
2371
+ apex: {
2372
+ ...(current?.dnsRecords?.apex || {}),
2373
+ enabled: !!current.dnsEligible && !!current?.dnsRecords?.apex?.value && apexAdded,
2374
+ },
2375
+ www: {
2376
+ ...(current?.dnsRecords?.www || {}),
2377
+ enabled: !!current.dnsEligible && !!current?.dnsRecords?.www?.value,
2378
+ },
2379
+ }
2380
+
2381
+ registryStateByDomain.set(domain, {
2382
+ ...current,
2383
+ dnsRecords: nextDnsRecords,
2384
+ wwwAdded,
2385
+ wwwError,
2386
+ apexAttempted: !!plan.apexAttempted,
2387
+ apexAdded,
2388
+ apexError,
2389
+ dnsGuidance,
2390
+ })
2158
2391
  }
2159
- return
2160
2392
  }
2161
2393
 
2162
- const results = await Promise.all(syncDomains.map(domain => addCloudflarePagesDomain(domain, { orgId, siteId })))
2163
- const failed = results
2164
- .map((result, index) => ({ result, domain: syncDomains[index] }))
2165
- .filter(item => !item.result?.ok)
2394
+ if (registryStateByDomain.size) {
2395
+ for (const [domain, value] of registryStateByDomain.entries()) {
2396
+ const registryRef = db.collection(DOMAIN_REGISTRY_COLLECTION).doc(domain)
2397
+ const payload = {
2398
+ domain,
2399
+ orgId,
2400
+ siteId,
2401
+ sitePath: siteRef.path,
2402
+ updatedAt: Firestore.FieldValue.serverTimestamp(),
2403
+ apexDomain: value.apexDomain || '',
2404
+ wwwDomain: value.wwwDomain || '',
2405
+ dnsEligible: !!value.dnsEligible,
2406
+ apexAttempted: !!value.apexAttempted,
2407
+ apexAdded: !!value.apexAdded,
2408
+ wwwAdded: !!value.wwwAdded,
2409
+ dnsRecords: value.dnsRecords || {},
2410
+ dnsGuidance: value.dnsGuidance || '',
2411
+ }
2412
+ payload.apexError = value.apexError ? value.apexError : Firestore.FieldValue.delete()
2413
+ payload.wwwError = value.wwwError ? value.wwwError : Firestore.FieldValue.delete()
2414
+ await registryRef.set(payload, { merge: true })
2415
+ }
2416
+ }
2166
2417
 
2418
+ const failed = syncResults.filter(item => !item.wwwResult?.ok)
2167
2419
  if (!failed.length) {
2168
- if (!conflictDomains.length && siteData.domainError && !domainErrorChanged) {
2420
+ if (!conflictDomains.length && siteData.domainError) {
2169
2421
  await siteRef.set({ domainError: Firestore.FieldValue.delete() }, { merge: true })
2170
2422
  }
2171
2423
  return
2172
2424
  }
2173
2425
 
2174
- const errorDomains = failed.map(item => item.domain)
2426
+ const errorDomains = failed.map(item => item.wwwDomain)
2175
2427
  const errorDetails = failed
2176
- .map(item => item.result?.error)
2428
+ .map(item => item.wwwResult?.error)
2177
2429
  .filter(Boolean)
2178
2430
  .join('; ')
2179
2431
  const cloudflareMessage = `Cloudflare domain sync failed for "${errorDomains.join(', ')}". ${errorDetails || 'Check function logs.'}`.trim()
@@ -14,6 +14,13 @@ function formatPhoneNumber(phone) {
14
14
  return `+1${numericPhone}`
15
15
  }
16
16
 
17
+ const assertCallableUser = (request) => {
18
+ if (!request?.auth?.uid)
19
+ throw new HttpsError('unauthenticated', 'Authentication required.')
20
+ if (request?.data?.uid !== request.auth.uid)
21
+ throw new HttpsError('permission-denied', 'UID mismatch.')
22
+ }
23
+
17
24
  exports.uploadDocumentDeleted = onDocumentDeleted(
18
25
  { document: 'organizations/{orgId}/files/{docId}', timeoutSeconds: 180 },
19
26
  async (event) => {
@@ -34,21 +41,19 @@ exports.uploadDocumentDeleted = onDocumentDeleted(
34
41
  )
35
42
 
36
43
  exports.addUpdateFileDoc = onCall(async (request) => {
44
+ assertCallableUser(request)
37
45
  const data = request.data
38
- const auth = request.auth
39
46
  let docId = data?.docId
40
- if (data.uid === auth.uid) {
41
- console.log(data)
42
- const orgId = data.orgId
43
- if (docId) {
44
- const docRef = db.collection(`organizations/${orgId}/files`).doc(docId)
45
- await docRef.set(data, { merge: true })
46
- }
47
- else {
48
- const docRef = db.collection(`organizations/${orgId}/files`).doc()
49
- await docRef.set(data)
50
- docId = docRef.id
51
- }
47
+ console.log(data)
48
+ const orgId = data.orgId
49
+ if (docId) {
50
+ const docRef = db.collection(`organizations/${orgId}/files`).doc(docId)
51
+ await docRef.set(data, { merge: true })
52
+ }
53
+ else {
54
+ const docRef = db.collection(`organizations/${orgId}/files`).doc()
55
+ await docRef.set(data)
56
+ docId = docRef.id
52
57
  }
53
58
  console.log(docId)
54
59
  return { docId }
@@ -426,91 +431,87 @@ exports.initFirestore = onCall(async (request) => {
426
431
  })
427
432
 
428
433
  exports.removeNonRegisteredUser = onCall(async (request) => {
434
+ assertCallableUser(request)
429
435
  const data = request.data
430
- const auth = request.auth
431
- if (data.uid === auth.uid) {
432
- const stagedUser = await db.collection('staged-users').doc(data.docId).get()
433
- if (stagedUser.exists) {
434
- const stagedUserData = stagedUser.data()
435
-
436
- const rolesExist = stagedUserData.roles && Object.keys(stagedUserData.roles).length !== 0
437
- const specialPermissionsExist = stagedUserData.specialPermissions && Object.keys(stagedUserData.specialPermissions).length !== 0
438
- const userIdExistsAndNotBlank = stagedUserData.userId && stagedUserData.userId !== ''
439
-
440
- if (!rolesExist && !specialPermissionsExist && !userIdExistsAndNotBlank) {
441
- await db.collection('staged-users').doc(data.docId).delete()
442
- return { success: true, message: '' }
436
+ const stagedUser = await db.collection('staged-users').doc(data.docId).get()
437
+ if (stagedUser.exists) {
438
+ const stagedUserData = stagedUser.data()
439
+
440
+ const rolesExist = stagedUserData.roles && Object.keys(stagedUserData.roles).length !== 0
441
+ const specialPermissionsExist = stagedUserData.specialPermissions && Object.keys(stagedUserData.specialPermissions).length !== 0
442
+ const userIdExistsAndNotBlank = stagedUserData.userId && stagedUserData.userId !== ''
443
+
444
+ if (!rolesExist && !specialPermissionsExist && !userIdExistsAndNotBlank) {
445
+ await db.collection('staged-users').doc(data.docId).delete()
446
+ return { success: true, message: '' }
447
+ }
448
+ else {
449
+ let message = ''
450
+ if (rolesExist && specialPermissionsExist) {
451
+ message = 'Cannot delete because the non-registered user still has roles and special permissions assigned.'
443
452
  }
444
- else {
445
- let message = ''
446
- if (rolesExist && specialPermissionsExist) {
447
- message = 'Cannot delete because the non-registered user still has roles and special permissions assigned.'
448
- }
449
- else if (rolesExist) {
450
- message = 'Cannot delete because the non-registered user still has roles assigned.'
451
- }
452
- else if (specialPermissionsExist) {
453
- message = 'Cannot delete because the non-registered user still has special permissions assigned.'
454
- }
455
- else if (userIdExistsAndNotBlank) {
456
- message = 'Cannot delete because the user is registered.'
457
- }
458
- return { success: false, message }
453
+ else if (rolesExist) {
454
+ message = 'Cannot delete because the non-registered user still has roles assigned.'
455
+ }
456
+ else if (specialPermissionsExist) {
457
+ message = 'Cannot delete because the non-registered user still has special permissions assigned.'
458
+ }
459
+ else if (userIdExistsAndNotBlank) {
460
+ message = 'Cannot delete because the user is registered.'
459
461
  }
462
+ return { success: false, message }
460
463
  }
461
464
  }
462
465
  return { success: false, message: 'Non-registered user not found.' }
463
466
  })
464
467
 
465
468
  exports.currentUserRegister = onCall(async (request) => {
469
+ assertCallableUser(request)
466
470
  const data = request.data
467
- const auth = request.auth
468
- if (data.uid === auth.uid) {
469
- const stagedUser = await db.collection('staged-users').doc(data.registrationCode).get()
470
- if (!stagedUser.exists) {
471
- return { success: false, message: 'Registration code not found.' }
471
+ const stagedUser = await db.collection('staged-users').doc(data.registrationCode).get()
472
+ if (!stagedUser.exists) {
473
+ return { success: false, message: 'Registration code not found.' }
474
+ }
475
+ else {
476
+ const stagedUserData = await stagedUser.data()
477
+ let process = false
478
+ if (stagedUserData.isTemplate) {
479
+ process = true
472
480
  }
473
- else {
474
- const stagedUserData = await stagedUser.data()
475
- let process = false
476
- if (stagedUserData.isTemplate) {
477
- process = true
478
- }
479
- if (!stagedUserData.isTemplate && stagedUserData.userId === '') {
480
- process = true
481
- }
482
- if (!process) {
483
- return { success: false, message: 'Registration code not valid.' }
484
- }
485
- const newRoles = stagedUserData.roles || {}
486
- const currentUser = await db.collection('users').doc(data.uid).get()
487
- const currentUserData = await currentUser.data()
488
- const currentRoles = currentUserData.roles || {}
489
- const currentUserCollectionPaths = currentUserData.collectionPaths || []
490
- let newRole = {}
491
- if (stagedUserData.subCreate && Object.keys(stagedUserData.subCreate).length !== 0 && stagedUserData.isTemplate) {
492
- if (!data.dynamicDocumentFieldValue) {
493
- return { success: false, message: 'Dynamic document field value is required.' }
494
- }
495
- const rootPath = stagedUserData.subCreate.rootPath
496
- const newDoc = stagedUserData.subCreate.documentStructure
497
- newDoc[stagedUserData.subCreate.dynamicDocumentField] = data.dynamicDocumentFieldValue
498
- const addedDoc = await db.collection(rootPath).add(newDoc)
499
- await db.collection(rootPath).doc(addedDoc.id).update({ docId: addedDoc.id })
500
- newRole = { [`${rootPath}-${addedDoc.id}`]: { collectionPath: `${rootPath}-${addedDoc.id}`, role: stagedUserData.subCreate.role } }
481
+ if (!stagedUserData.isTemplate && stagedUserData.userId === '') {
482
+ process = true
483
+ }
484
+ if (!process) {
485
+ return { success: false, message: 'Registration code not valid.' }
486
+ }
487
+ const newRoles = stagedUserData.roles || {}
488
+ const currentUser = await db.collection('users').doc(data.uid).get()
489
+ const currentUserData = await currentUser.data()
490
+ const currentRoles = currentUserData.roles || {}
491
+ const currentUserCollectionPaths = currentUserData.collectionPaths || []
492
+ let newRole = {}
493
+ if (stagedUserData.subCreate && Object.keys(stagedUserData.subCreate).length !== 0 && stagedUserData.isTemplate) {
494
+ if (!data.dynamicDocumentFieldValue) {
495
+ return { success: false, message: 'Dynamic document field value is required.' }
501
496
  }
502
- const combinedRoles = { ...currentRoles, ...newRoles, ...newRole }
503
- Object.values(combinedRoles).forEach((role) => {
504
- if (!currentUserCollectionPaths.includes(role.collectionPath)) {
505
- currentUserCollectionPaths.push(role.collectionPath)
506
- }
507
- })
508
- await db.collection('staged-users').doc(currentUserData.stagedDocId).update({ roles: combinedRoles, collectionPaths: currentUserCollectionPaths })
509
- if (!stagedUserData.isTemplate) {
510
- await db.collection('staged-users').doc(data.registrationCode).delete()
497
+ const rootPath = stagedUserData.subCreate.rootPath
498
+ const newDoc = stagedUserData.subCreate.documentStructure
499
+ newDoc[stagedUserData.subCreate.dynamicDocumentField] = data.dynamicDocumentFieldValue
500
+ const addedDoc = await db.collection(rootPath).add(newDoc)
501
+ await db.collection(rootPath).doc(addedDoc.id).update({ docId: addedDoc.id })
502
+ newRole = { [`${rootPath}-${addedDoc.id}`]: { collectionPath: `${rootPath}-${addedDoc.id}`, role: stagedUserData.subCreate.role } }
503
+ }
504
+ const combinedRoles = { ...currentRoles, ...newRoles, ...newRole }
505
+ Object.values(combinedRoles).forEach((role) => {
506
+ if (!currentUserCollectionPaths.includes(role.collectionPath)) {
507
+ currentUserCollectionPaths.push(role.collectionPath)
511
508
  }
512
- return { success: true, message: '' }
509
+ })
510
+ await db.collection('staged-users').doc(currentUserData.stagedDocId).update({ roles: combinedRoles, collectionPaths: currentUserCollectionPaths })
511
+ if (!stagedUserData.isTemplate) {
512
+ await db.collection('staged-users').doc(data.registrationCode).delete()
513
513
  }
514
+ return { success: true, message: '' }
514
515
  }
515
516
  })
516
517
 
@@ -522,52 +523,51 @@ exports.checkOrgIdExists = onCall(async (request) => {
522
523
  })
523
524
 
524
525
  exports.deleteSelf = onCall(async (request) => {
525
- if (request.data.uid === request.auth.uid) {
526
- try {
527
- const userDoc = await db.collection('staged-users').doc(request.auth.uid).get()
528
- const userData = userDoc.data()
529
- const userCollectionPaths = userData.collectionPaths || []
530
-
531
- for (const path of userCollectionPaths) {
532
- const usersWithSamePath = await db.collection('staged-users').where('collectionPaths', 'array-contains', path).get()
533
-
534
- // If no other users have the same collection path, delete the path and all documents and collections under it
535
- if (usersWithSamePath.size <= 1) {
536
- const adjustedPath = path.replace(/-/g, '/')
537
- const docRef = db.doc(adjustedPath)
538
- const doc = await docRef.get()
539
-
540
- if (doc.exists) {
541
- // If the path is a document, delete it directly
542
- await docRef.delete()
543
- }
544
- else {
545
- // If the path is a collection, delete all documents under it
546
- const docsToDelete = await db.collection(adjustedPath).get()
547
- const batch = db.batch()
548
- docsToDelete.docs.forEach((doc) => {
549
- batch.delete(doc.ref)
550
- })
551
- await batch.commit()
552
- }
526
+ assertCallableUser(request)
527
+ try {
528
+ const userDoc = await db.collection('staged-users').doc(request.auth.uid).get()
529
+ const userData = userDoc.data()
530
+ const userCollectionPaths = userData.collectionPaths || []
531
+
532
+ for (const path of userCollectionPaths) {
533
+ const usersWithSamePath = await db.collection('staged-users').where('collectionPaths', 'array-contains', path).get()
534
+
535
+ // If no other users have the same collection path, delete the path and all documents and collections under it
536
+ if (usersWithSamePath.size <= 1) {
537
+ const adjustedPath = path.replace(/-/g, '/')
538
+ const docRef = db.doc(adjustedPath)
539
+ const doc = await docRef.get()
540
+
541
+ if (doc.exists) {
542
+ // If the path is a document, delete it directly
543
+ await docRef.delete()
544
+ }
545
+ else {
546
+ // If the path is a collection, delete all documents under it
547
+ const docsToDelete = await db.collection(adjustedPath).get()
548
+ const batch = db.batch()
549
+ docsToDelete.docs.forEach((doc) => {
550
+ batch.delete(doc.ref)
551
+ })
552
+ await batch.commit()
553
553
  }
554
554
  }
555
+ }
555
556
 
556
- // Delete from 'staged-users' collection
557
- await db.collection('staged-users').doc(request.data.uid).delete()
557
+ // Delete from 'staged-users' collection
558
+ await db.collection('staged-users').doc(request.data.uid).delete()
558
559
 
559
- // Delete from 'users' collection
560
- await db.collection('users').doc(request.data.uid).delete()
560
+ // Delete from 'users' collection
561
+ await db.collection('users').doc(request.data.uid).delete()
561
562
 
562
- // Delete the user from Firebase
563
- await admin.auth().deleteUser(request.data.uid)
563
+ // Delete the user from Firebase
564
+ await admin.auth().deleteUser(request.data.uid)
564
565
 
565
- return { success: true }
566
- }
567
- catch (error) {
568
- console.error('Error deleting user:', error)
569
- return { success: false, error }
570
- }
566
+ return { success: true }
567
+ }
568
+ catch (error) {
569
+ console.error('Error deleting user:', error)
570
+ return { success: false, error }
571
571
  }
572
572
  })
573
573