@edgedev/create-edge-app 1.0.46 → 1.0.47

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.
@@ -4,7 +4,7 @@
4
4
  "description": "Cloud Functions for Firebase",
5
5
  "main": "index.js",
6
6
  "engines": {
7
- "node": "18"
7
+ "node": "16"
8
8
  },
9
9
  "scripts": {
10
10
  "serve": "firebase emulators:start --only functions",
@@ -14,18 +14,22 @@
14
14
  "logs": "firebase functions:log"
15
15
  },
16
16
  "dependencies": {
17
+ "@aws-sdk/client-s3": "^3.582.0",
18
+ "@aws-sdk/s3-request-presigner": "^3.582.0",
17
19
  "@google-cloud/pubsub": "^4.9.0",
20
+ "aws-sdk": "^2.1692.0",
21
+ "axios": "^1.7.2",
18
22
  "crypto": "^1.0.1",
19
- "dotenv": "^16.3.1",
23
+ "dotenv": "^16.4.7",
20
24
  "exceljs": "^4.4.0",
21
25
  "firebase-admin": "^13.0.2",
22
26
  "firebase-functions": "^6.2.0",
23
- "form-data": "^4.0.0",
27
+ "form-data": "^4.0.1",
24
28
  "formidable-serverless": "^1.1.1",
25
- "moment-timezone": "^0.5.43",
26
- "openai": "^4.11.1",
27
- "stripe": "^13.8.0",
28
- "twilio": "^4.18.0"
29
+ "moment-timezone": "^0.5.46",
30
+ "openai": "^4.78.1",
31
+ "stripe": "^13.11.0",
32
+ "twilio": "^4.23.0"
29
33
  },
30
34
  "devDependencies": {
31
35
  "firebase-functions-test": "^0.2.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edgedev/create-edge-app",
3
- "version": "1.0.46",
3
+ "version": "1.0.47",
4
4
  "description": "Create Edge Starter App",
5
5
  "bin": {
6
6
  "create-edge-app": "./bin/cli.js"
@@ -1,87 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-var-requires */
2
- /* eslint-disable no-undef */
3
- const functions = require('firebase-functions')
4
- const { PubSub } = require('@google-cloud/pubsub')
5
- const admin = require('firebase-admin')
6
-
7
- const pubsub = new PubSub()
8
-
9
- admin.initializeApp()
10
-
11
- const { onMessagePublished } = require('firebase-functions/v2/pubsub')
12
-
13
- const { onCall, HttpsError, onRequest } = require('firebase-functions/v2/https')
14
- const { onSchedule } = require('firebase-functions/v2/scheduler')
15
- const { Storage } = require('@google-cloud/storage')
16
- const {
17
- onDocumentWritten,
18
- onDocumentCreated,
19
- onDocumentUpdated,
20
- onDocumentDeleted,
21
- Change,
22
- FirestoreEvent,
23
- } = require('firebase-functions/v2/firestore')
24
- const { logger } = require('firebase-functions/v2')
25
- const { getFirestore } = require('firebase-admin/firestore')
26
- const twilio = require('twilio')
27
- const db = getFirestore()
28
-
29
- // The permissionCheck function
30
-
31
- const permissions = {
32
- 'admin': {'assign': true, 'delete': true, 'read': true, 'write': true},
33
- 'editor': {'assign': false, 'delete': true, 'read': true, 'write': true},
34
- 'user': {'assign': false, 'delete': false, 'read': true, 'write': false},
35
- 'writer': {'assign': false, 'delete': false, 'read': true, 'write': true}
36
- };
37
-
38
- const permissionCheck = async (userId, action, originalFilePath) => {
39
- // Fetch user document
40
- const collectionPath = originalFilePath.replace(/\//g, '-')
41
- const userDoc = await db.collection('users').doc(userId).get()
42
- if (!userDoc.exists) {
43
- console.log('No such user!')
44
- return false // Or handle as needed
45
- }
46
- const userData = userDoc.data()
47
-
48
- // Fetch roles from user data
49
- const roles = Object.values(userData.roles || {})
50
-
51
- for (const role of roles) {
52
- // Check if the role's collectionPath is a prefix of the collectionPath
53
- if (collectionPath.startsWith(role.collectionPath)) {
54
- // Use permissions object instead of fetching collection data
55
- const rolePermissions = permissions[role.role];
56
- if (rolePermissions && rolePermissions[action]) {
57
- return true;
58
- }
59
- }
60
- }
61
- return false;
62
- }
63
-
64
- module.exports = {
65
- pubsub,
66
- onMessagePublished,
67
- onRequest,
68
- onSchedule,
69
- onDocumentWritten,
70
- onDocumentCreated,
71
- onDocumentUpdated,
72
- onDocumentDeleted,
73
- Change,
74
- FirestoreEvent,
75
- onCall,
76
- HttpsError,
77
- logger,
78
- getFirestore,
79
- functions,
80
- admin,
81
- twilio,
82
- db,
83
- Storage,
84
- permissionCheck,
85
- }
86
-
87
-
@@ -1,424 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-var-requires */
2
- /* eslint-disable no-undef */
3
- const { onCall, HttpsError, logger, getFirestore, functions, admin, twilio, db, onSchedule, onDocumentUpdated, pubsub, Storage, permissionCheck } = require('./config.js')
4
-
5
- const authToken = process.env.TWILIO_AUTH_TOKEN
6
- const accountSid = process.env.TWILIO_SID
7
- const systemNumber = process.env.TWILIO_SYSTEM_NUMBER
8
-
9
- function formatPhoneNumber(phone) {
10
- // Remove non-numeric characters from the phone number
11
- const numericPhone = phone.replace(/\D/g, '')
12
- // Return the formatted number
13
- return `+1${numericPhone}`
14
- }
15
-
16
- exports.topicQueue = onSchedule({ schedule: 'every 1 minutes', timeoutSeconds: 180 }, async (event) => {
17
- const queuedTopicsRef = db.collection('topic-queue')
18
- const snapshot = await queuedTopicsRef.get()
19
-
20
- for (const doc of snapshot.docs) {
21
- await db.runTransaction(async (transaction) => {
22
- const docSnapshot = await transaction.get(doc.ref)
23
- if (!docSnapshot.exists) {
24
- throw new Error('Document does not exist!')
25
- }
26
- const docData = docSnapshot.data()
27
- const emailTimestamp = docData.timestamp ? docData.timestamp.toMillis() : 0
28
- const delayTimestamp = docData.minuteDelay ? emailTimestamp + docData.minuteDelay * 60 * 1000 : 0
29
- const currentTimestamp = Date.now()
30
- // Check if current time is beyond the timestamp + minuteDelay, or if timestamp or minuteDelay is not set
31
- if (emailTimestamp > currentTimestamp || currentTimestamp >= delayTimestamp || !docData.timestamp || !docData.minuteDelay) {
32
- // Check if topic and payload exist and are not empty
33
- if (docData.topic && docData.payload && typeof docData.payload === 'object' && docData.topic.trim() !== '') {
34
- try {
35
- await pubsub.topic(docData.topic).publishMessage({ data: Buffer.from(JSON.stringify(docData.payload)) })
36
- // Delete the document after successfully publishing the message
37
- transaction.delete(doc.ref)
38
- }
39
- catch (error) {
40
- console.error(`Error publishing message to topic ${docData.topic}:`, error)
41
- // Increment retry count and set new delay
42
- const retryCount = docData.retry ? docData.retry + 1 : 1
43
- if (retryCount <= 3) {
44
- const minuteDelay = retryCount === 1 ? 1 : retryCount === 2 ? 10 : 30
45
- transaction.update(doc.ref, { retry: retryCount, minuteDelay })
46
- }
47
- else {
48
- // Delete the document if there was an error publishing the topic after 3 retries
49
- transaction.delete(doc.ref)
50
- }
51
- }
52
- }
53
- // Delete the document if topic or payload does not exist or is empty
54
- else {
55
- transaction.delete(doc.ref)
56
- }
57
- }
58
- })
59
- }
60
- })
61
-
62
- exports.sendVerificationCode = onCall(async (request) => {
63
- const data = request.data
64
- let code = (Math.floor(Math.random() * 1000000) + 1000000).toString().substring(1)
65
- const phone = formatPhoneNumber(data.phone)
66
-
67
- if (phone === '+19999999999') {
68
- code = '123456'
69
- }
70
- else {
71
- try {
72
- const client = twilio(accountSid, authToken)
73
- await client.messages.create({
74
- body: `Your verification code is: ${code}`,
75
- to: phone, // the user's phone number
76
- from: systemNumber, // your Twilio phone number from the configuration
77
- })
78
- }
79
- catch (error) {
80
- console.log(error)
81
- return { success: false, error: 'Invalid Phone #' }
82
- }
83
- }
84
-
85
- try {
86
- // Use the formatted phone number as the document ID for Firestore
87
- await db.collection('phone-auth').doc(phone).set({
88
- phone,
89
- code,
90
- })
91
- return phone
92
- }
93
- catch (error) {
94
- return { success: false, error }
95
- }
96
- })
97
-
98
- exports.verifyPhoneNumber = onCall(async (request) => {
99
- const data = request.data
100
- const phone = data.phone
101
- const code = data.code
102
-
103
- // Get the phone-auth document with the given phone number
104
- const phoneDoc = await db.collection('phone-auth').doc(phone).get()
105
-
106
- if (!phoneDoc.exists) {
107
- return { success: false, error: 'Phone number not found.' }
108
- }
109
-
110
- const storedCode = phoneDoc.data().code
111
-
112
- if (storedCode !== code) {
113
- return { success: false, error: 'Invalid verification code.' }
114
- }
115
-
116
- // If the code matches, authenticate the user with Firebase Custom Auth
117
- try {
118
- // You would typically generate a UID based on the phone number or another system
119
- const uid = phone
120
-
121
- // Create a custom token (this can be used on the client to sign in)
122
- const customToken = await admin.auth().createCustomToken(uid)
123
-
124
- return { success: true, token: customToken }
125
- }
126
- catch (error) {
127
- console.error('Error creating custom token:', error)
128
- return { success: false, error: 'Failed to authenticate.' }
129
- }
130
- })
131
-
132
- exports.initFirestore = onCall(async (request) => {
133
- // checks to see of the collections 'staged-users' exist if not will seed them with data
134
- const stagedUsers = await db.collection('staged-users').get()
135
- if (stagedUsers.empty) {
136
- const templateUser = {
137
- docId: 'organization-registration-template',
138
- isTemplate: true,
139
- meta: {
140
- name: 'Organization Registration Template',
141
- },
142
- subCreate: {
143
- documentStructure: {
144
- name: '',
145
- },
146
- dynamicDocumentField: 'name',
147
- role: 'admin',
148
- rootPath: 'organizations',
149
- },
150
- userId: '',
151
- }
152
- await db.collection('staged-users').doc('organization-registration-template').set(templateUser)
153
- }
154
- })
155
-
156
- exports.removeNonRegisteredUser = onCall(async (request) => {
157
- const data = request.data
158
- const auth = request.auth
159
- if (data.uid === auth.uid) {
160
- const stagedUser = await db.collection('staged-users').doc(data.docId).get()
161
- if (stagedUser.exists) {
162
- const stagedUserData = stagedUser.data()
163
-
164
- const rolesExist = stagedUserData.roles && Object.keys(stagedUserData.roles).length !== 0
165
- const specialPermissionsExist = stagedUserData.specialPermissions && Object.keys(stagedUserData.specialPermissions).length !== 0
166
- const userIdExistsAndNotBlank = stagedUserData.userId && stagedUserData.userId !== ''
167
-
168
- if (!rolesExist && !specialPermissionsExist && !userIdExistsAndNotBlank) {
169
- await db.collection('staged-users').doc(data.docId).delete()
170
- return { success: true, message: '' }
171
- }
172
- else {
173
- let message = ''
174
- if (rolesExist && specialPermissionsExist) {
175
- message = 'Cannot delete because the non-registered user still has roles and special permissions assigned.'
176
- }
177
- else if (rolesExist) {
178
- message = 'Cannot delete because the non-registered user still has roles assigned.'
179
- }
180
- else if (specialPermissionsExist) {
181
- message = 'Cannot delete because the non-registered user still has special permissions assigned.'
182
- }
183
- else if (userIdExistsAndNotBlank) {
184
- message = 'Cannot delete because the user is registered.'
185
- }
186
- return { success: false, message }
187
- }
188
- }
189
- }
190
- return { success: false, message: 'Non-registered user not found.' }
191
- })
192
-
193
- exports.currentUserRegister = onCall(async (request) => {
194
- const data = request.data
195
- const auth = request.auth
196
- if (data.uid === auth.uid) {
197
- const stagedUser = await db.collection('staged-users').doc(data.registrationCode).get()
198
- if (!stagedUser.exists) {
199
- return { success: false, message: 'Registration code not found.' }
200
- }
201
- else {
202
- const stagedUserData = await stagedUser.data()
203
- let process = false
204
- if (stagedUserData.isTemplate) {
205
- process = true
206
- }
207
- if (!stagedUserData.isTemplate && stagedUserData.userId === '') {
208
- process = true
209
- }
210
- if (!process) {
211
- return { success: false, message: 'Registration code not valid.' }
212
- }
213
- const newRoles = stagedUserData.roles || {}
214
- const currentUser = await db.collection('users').doc(data.uid).get()
215
- const currentUserData = await currentUser.data()
216
- const currentRoles = currentUserData.roles || {}
217
- const currentUserCollectionPaths = currentUserData.collectionPaths || []
218
- let newRole = {}
219
- if (stagedUserData.subCreate && Object.keys(stagedUserData.subCreate).length !== 0 && stagedUserData.isTemplate) {
220
- if (!data.dynamicDocumentFieldValue) {
221
- return { success: false, message: 'Dynamic document field value is required.' }
222
- }
223
- const rootPath = stagedUserData.subCreate.rootPath
224
- const newDoc = stagedUserData.subCreate.documentStructure
225
- newDoc[stagedUserData.subCreate.dynamicDocumentField] = data.dynamicDocumentFieldValue
226
- const addedDoc = await db.collection(rootPath).add(newDoc)
227
- await db.collection(rootPath).doc(addedDoc.id).update({ docId: addedDoc.id })
228
- newRole = { [`${rootPath}-${addedDoc.id}`]: { collectionPath: `${rootPath}-${addedDoc.id}`, role: stagedUserData.subCreate.role } }
229
- }
230
- const combinedRoles = { ...currentRoles, ...newRoles, ...newRole }
231
- Object.values(combinedRoles).forEach((role) => {
232
- if (!currentUserCollectionPaths.includes(role.collectionPath)) {
233
- currentUserCollectionPaths.push(role.collectionPath)
234
- }
235
- })
236
- await db.collection('staged-users').doc(currentUserData.stagedDocId).update({ roles: combinedRoles, collectionPaths: currentUserCollectionPaths })
237
- if (!stagedUserData.isTemplate) {
238
- await db.collection('staged-users').doc(data.registrationCode).delete()
239
- }
240
- return { success: true, message: '' }
241
- }
242
- }
243
- })
244
-
245
- exports.checkOrgIdExists = onCall(async (request) => {
246
- const data = request.data
247
- const orgId = data.orgId.toLowerCase()
248
- const orgDoc = await db.collection('organizations').doc(orgId).get()
249
- return { exists: orgDoc.exists }
250
- })
251
-
252
- exports.deleteSelf = onCall(async (request) => {
253
- if (request.data.uid === request.auth.uid) {
254
- try {
255
- const userDoc = await db.collection('staged-users').doc(request.auth.uid).get()
256
- const userData = userDoc.data()
257
- const userCollectionPaths = userData.collectionPaths || []
258
-
259
- for (const path of userCollectionPaths) {
260
- const usersWithSamePath = await db.collection('staged-users').where('collectionPaths', 'array-contains', path).get()
261
-
262
- // If no other users have the same collection path, delete the path and all documents and collections under it
263
- if (usersWithSamePath.size <= 1) {
264
- const adjustedPath = path.replace(/-/g, '/')
265
- const docRef = db.doc(adjustedPath)
266
- const doc = await docRef.get()
267
-
268
- if (doc.exists) {
269
- // If the path is a document, delete it directly
270
- await docRef.delete()
271
- }
272
- else {
273
- // If the path is a collection, delete all documents under it
274
- const docsToDelete = await db.collection(adjustedPath).get()
275
- const batch = db.batch()
276
- docsToDelete.docs.forEach((doc) => {
277
- batch.delete(doc.ref)
278
- })
279
- await batch.commit()
280
- }
281
- }
282
- }
283
-
284
- // Delete from 'staged-users' collection
285
- await db.collection('staged-users').doc(request.data.uid).delete()
286
-
287
- // Delete from 'users' collection
288
- await db.collection('users').doc(request.data.uid).delete()
289
-
290
- // Delete the user from Firebase
291
- await admin.auth().deleteUser(request.data.uid)
292
-
293
- return { success: true }
294
- }
295
- catch (error) {
296
- console.error('Error deleting user:', error)
297
- return { success: false, error }
298
- }
299
- }
300
- })
301
-
302
- exports.updateUser = onDocumentUpdated({ document: 'staged-users/{docId}', timeoutSeconds: 180 }, async (event) => {
303
- const change = event.data
304
- const eventId = event.id
305
- const eventRef = db.collection('events').doc(eventId)
306
- const stagedDocId = event.params.docId
307
- let newData = change.after.data()
308
- const oldData = change.before.data()
309
-
310
- const shouldProcess = await eventRef.get().then((eventDoc) => {
311
- return !eventDoc.exists || !eventDoc.data().processed
312
- })
313
-
314
- if (!shouldProcess) {
315
- return null
316
- }
317
-
318
- // Note: we can trust on newData.uid because we are checking in rules that it matches the auth.uid
319
- if (newData.userId) {
320
- const userRef = db.collection('users').doc(newData.userId)
321
- await setUser(userRef, newData, oldData, stagedDocId)
322
- await markProcessed(eventRef)
323
- }
324
- else {
325
- if (newData.templateUserId !== oldData.templateUserId) {
326
- // Check if templateUserId already exists in the staged-users collection
327
- const stagedUserRef = db.collection('staged-users').doc(newData.templateUserId)
328
- const doc = await stagedUserRef.get()
329
-
330
- // If it exists, skip the creation process
331
- if (doc.exists) {
332
- return null
333
- }
334
-
335
- newData.isTemplate = false
336
- const templateUserId = newData.templateUserId
337
- newData.meta = newData.templateMeta
338
- delete newData.templateMeta
339
- delete newData.templateUserId
340
- if (Object.prototype.hasOwnProperty.call(newData, 'subCreate') && Object.values(newData.subCreate).length > 0) {
341
- const subCreate = newData.subCreate
342
- delete newData.subCreate
343
- let newDocId = ''
344
- if (Object.prototype.hasOwnProperty.call(newData, 'requestedOrgId')) {
345
- newDocId = newData.requestedOrgId.toLowerCase()
346
- delete newData.requestedOrgId
347
- }
348
- let addedDoc
349
- if (newDocId) {
350
- const docRef = db.collection(subCreate.rootPath).doc(newDocId)
351
- const doc = await docRef.get()
352
- if (!doc.exists) {
353
- await docRef.set({ [subCreate.dynamicDocumentField]: newData.dynamicDocumentFieldValue })
354
- addedDoc = docRef
355
- }
356
- else {
357
- addedDoc = await db.collection(subCreate.rootPath).add({ [subCreate.dynamicDocumentField]: newData.dynamicDocumentFieldValue })
358
- }
359
- }
360
- else {
361
- addedDoc = await db.collection(subCreate.rootPath).add({ [subCreate.dynamicDocumentField]: newData.dynamicDocumentFieldValue })
362
- }
363
- await db.collection(subCreate.rootPath).doc(addedDoc.id).update({ docId: addedDoc.id })
364
- delete newData.dynamicDocumentFieldValue
365
- const newRole = { [`${subCreate.rootPath}-${addedDoc.id}`]: { collectionPath: `${subCreate.rootPath}-${addedDoc.id}`, role: subCreate.role } }
366
- if (Object.prototype.hasOwnProperty.call(newData, 'collectionPaths')) {
367
- newData.collectionPaths.push(`${subCreate.rootPath}-${addedDoc.id}`)
368
- }
369
- else {
370
- newData.collectionPaths = [`${subCreate.rootPath}-${addedDoc.id}`]
371
- }
372
- const newRoles = { ...newData.roles, ...newRole }
373
- newData = { ...newData, roles: newRoles }
374
- const stagedUserRef = db.collection('staged-users').doc(templateUserId)
375
- await stagedUserRef.set({ ...newData, userId: templateUserId })
376
- const userRef = db.collection('users').doc(templateUserId)
377
- await setUser(userRef, newData, oldData, templateUserId)
378
- await markProcessed(eventRef)
379
- }
380
- else {
381
- const stagedUserRef = db.collection('staged-users').doc(templateUserId)
382
- await stagedUserRef.set({ ...newData, userId: templateUserId })
383
- const userRef = db.collection('users').doc(templateUserId)
384
- await setUser(userRef, newData, oldData, templateUserId)
385
- await markProcessed(eventRef)
386
- }
387
- }
388
- }
389
- await markProcessed(eventRef)
390
- })
391
-
392
- async function setUser(userRef, newData, oldData, stagedDocId) {
393
- const user = await userRef.get()
394
- let userUpdate = { meta: newData.meta, stagedDocId }
395
-
396
- if (newData.meta && newData.meta.name) {
397
- const publicUserRef = db.collection('public-users').doc(stagedDocId)
398
- const publicMeta = { name: newData.meta.name }
399
- publicUserRef.set({ uid: newData.uid, meta: publicMeta, collectionPaths: newData.collectionPaths, userId: stagedDocId })
400
- }
401
-
402
- if (Object.prototype.hasOwnProperty.call(newData, 'roles')) {
403
- userUpdate = { ...userUpdate, roles: newData.roles }
404
- }
405
- if (Object.prototype.hasOwnProperty.call(newData, 'specialPermissions')) {
406
- userUpdate = { ...userUpdate, specialPermissions: newData.specialPermissions }
407
- }
408
-
409
- if (!oldData.userId) {
410
- userUpdate = { ...userUpdate, userId: newData.uid }
411
- }
412
- if (!user.exists) {
413
- return userRef.set(userUpdate)
414
- }
415
- else {
416
- return userRef.update(userUpdate)
417
- }
418
- }
419
-
420
- function markProcessed(eventRef) {
421
- return eventRef.set({ processed: true }).then(() => {
422
- return null
423
- })
424
- }
@@ -1,8 +0,0 @@
1
- rules_version = '2';
2
- service firebase.storage {
3
- match /b/{bucket}/o {
4
- match /{allPaths=**} {
5
- allow read, write: if false;
6
- }
7
- }
8
- }