@edgedev/create-edge-app 1.0.17 → 1.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env ADDED
@@ -0,0 +1,11 @@
1
+ VITE_FIREBASE_API_KEY=
2
+ VITE_FIREBASE_AUTH_DOMAIN=
3
+ VITE_FIREBASE_PROJECT_ID=
4
+ VITE_FIREBASE_STORAGE_BUCKET=
5
+ VITE_FIREBASE_MESSAGING_SENDER_ID=
6
+ VITE_FIREBASE_APP_ID=
7
+ VITE_FIREBASE_MEASUREMENT_ID=
8
+ VITE_FIREBASE_EMULATOR_AUTH=
9
+ VITE_FIREBASE_EMULATOR_FIRESTORE=
10
+ VITE_FIREBASE_EMULATOR_FUNCTIONS=
11
+ REGISTRATION_CODE=organization-registration-template
package/.env.dev ADDED
@@ -0,0 +1,11 @@
1
+ VITE_FIREBASE_API_KEY=
2
+ VITE_FIREBASE_AUTH_DOMAIN=
3
+ VITE_FIREBASE_PROJECT_ID=
4
+ VITE_FIREBASE_STORAGE_BUCKET=
5
+ VITE_FIREBASE_MESSAGING_SENDER_ID=
6
+ VITE_FIREBASE_APP_ID=
7
+ VITE_FIREBASE_MEASUREMENT_ID=
8
+ VITE_FIREBASE_EMULATOR_AUTH=9099
9
+ VITE_FIREBASE_EMULATOR_FIRESTORE=8080
10
+ VITE_FIREBASE_EMULATOR_FUNCTIONS=5001
11
+ REGISTRATION_CODE=organization-registration-template
package/app.vue CHANGED
@@ -68,7 +68,7 @@ onMounted(() => {
68
68
  changeTheme('light')
69
69
  }
70
70
  })
71
- edgeFirebase.runFunction('initFirestore', {})
71
+ edgeFirebase.runFunction('edgeFirebase-initFirestore', {})
72
72
  edgeGlobal.edgeState.userRoles = [
73
73
  {
74
74
  name: 'Admin',
package/bin/cli.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  console.log('Script started')
3
3
  const { execSync } = require('child_process')
4
+ const fs = require('fs')
5
+ const path = require('path')
4
6
 
5
7
  const runCommand = (command) => {
6
8
  try {
@@ -14,10 +16,34 @@ const runCommand = (command) => {
14
16
  return true
15
17
  }
16
18
 
19
+ const modifyPackageJson = (repoName) => {
20
+ try {
21
+ const packageJsonPath = path.join(repoName, 'package.json')
22
+ const packageJsonData = fs.readFileSync(packageJsonPath, 'utf-8')
23
+ const packageJsonObj = JSON.parse(packageJsonData)
24
+
25
+ // Modify the properties
26
+ delete packageJsonObj.version
27
+ delete packageJsonObj.bin
28
+ packageJsonObj.name = repoName
29
+ packageJsonObj.description = `A really cool Edge App for ${repoName}`
30
+
31
+ // Write the file back
32
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonObj, null, 2))
33
+ }
34
+ catch (err) {
35
+ console.error('Failed to modify package.json', err)
36
+ return false
37
+ }
38
+ return true
39
+ }
40
+
17
41
  const repoName = process.argv[2]
18
42
  const gitCheckoutCommand = `git clone --depth 1 https://github.com/Edge-Marketing-and-Design/edgeApp.git ${repoName}`
43
+ const removeGitDirCommand = `rm -rf ${repoName}/.git`
19
44
  const installDependenciesCommand = `cd ${repoName} && pnpm install`
20
45
  const installFunctionDependenciesCommand = `cd ${repoName}/functions && npm install`
46
+ const cloneFirebaseFrameworkCommand = `cd ${repoName} && git clone https://github.com/Edge-Marketing-and-Design/edgeFirebaseFramework`
21
47
 
22
48
  console.log(`Cloning with name ${repoName}...`)
23
49
  const checkedOut = runCommand(gitCheckoutCommand)
@@ -25,6 +51,18 @@ if (!checkedOut) {
25
51
  process.exit(1)
26
52
  }
27
53
 
54
+ console.log(`Removing .git directory from ${repoName}...`)
55
+ const removedGitDir = runCommand(removeGitDirCommand)
56
+ if (!removedGitDir) {
57
+ process.exit(1)
58
+ }
59
+
60
+ console.log(`Modifying package.json for ${repoName}...`)
61
+ const modifiedPackageJson = modifyPackageJson(repoName)
62
+ if (!modifiedPackageJson) {
63
+ process.exit(1)
64
+ }
65
+
28
66
  console.log(`Installing dependencies for ${repoName}...`)
29
67
  const installedDeps = runCommand(installDependenciesCommand)
30
68
  if (!installedDeps) {
@@ -37,5 +75,11 @@ if (!installedFunctionDeps) {
37
75
  process.exit(1)
38
76
  }
39
77
 
78
+ console.log(`Cloning Firebase Framework inside ${repoName}...`)
79
+ const clonedFirebaseFramework = runCommand(cloneFirebaseFrameworkCommand)
80
+ if (!clonedFirebaseFramework) {
81
+ process.exit(1)
82
+ }
83
+
40
84
  console.log(`Successfully created ${repoName}!`)
41
85
  console.log(`cd into ${repoName} and run 'sh firebase_init.sh' to initialize your firebase project.`)
package/deploy.sh CHANGED
@@ -1,6 +1,5 @@
1
1
  pnpm run generate
2
- firebase functions:config:set openai.api_key="SOME_API_KEY"
3
- firebase functions:config:get
2
+ export NODE_ENV=production
4
3
  firebase deploy --only functions
5
4
  firebase deploy --only hosting
6
5
  firebase deploy --only firestore
package/emulator.sh CHANGED
@@ -14,4 +14,5 @@ DIR="./firebase_data"
14
14
  if [ ! -d "$DIR" ]; then
15
15
  cp -r ./firebase_data_emulator_seed ./firebase_data
16
16
  fi
17
+ export NODE_ENV=development
17
18
  firebase emulators:start --import ./firebase_data --export-on-exit
package/firebase_init.sh CHANGED
@@ -1,6 +1,18 @@
1
- # Prompt for the project ID
1
+ # Prompt for the Firebase configuration values
2
2
  echo "Please enter your Firebase project ID:"
3
3
  read project_id
4
+ echo "Please enter your Firebase API key:"
5
+ read api_key
6
+ echo "Please enter your Firebase Auth domain:"
7
+ read auth_domain
8
+ echo "Please enter your Firebase storage bucket:"
9
+ read storage_bucket
10
+ echo "Please enter your Firebase messaging sender ID:"
11
+ read messaging_sender_id
12
+ echo "Please enter your Firebase app ID:"
13
+ read app_id
14
+ echo "Please enter your Firebase measurement ID:"
15
+ read measurement_id
4
16
 
5
17
  # Check if project_id is empty
6
18
  if [ -z "$project_id" ]; then
@@ -36,4 +48,30 @@ fi
36
48
  # Restore firestore.rules from backup
37
49
  if [ -f ./firestore.rules.temp ]; then
38
50
  mv ./firestore.rules.temp ./firestore.rules
39
- fi
51
+ fi
52
+
53
+ # Create the .env file
54
+ echo "VITE_FIREBASE_API_KEY=$api_key" > .env
55
+ echo "VITE_FIREBASE_AUTH_DOMAIN=$auth_domain" >> .env
56
+ echo "VITE_FIREBASE_PROJECT_ID=$project_id" >> .env
57
+ echo "VITE_FIREBASE_STORAGE_BUCKET=$storage_bucket" >> .env
58
+ echo "VITE_FIREBASE_MESSAGING_SENDER_ID=$messaging_sender_id" >> .env
59
+ echo "VITE_FIREBASE_APP_ID=$app_id" >> .env
60
+ echo "VITE_FIREBASE_MEASUREMENT_ID=$measurement_id" >> .env
61
+ echo "VITE_FIREBASE_EMULATOR_AUTH=" >> .env
62
+ echo "VITE_FIREBASE_EMULATOR_FIRESTORE=" >> .env
63
+ echo "VITE_FIREBASE_EMULATOR_FUNCTIONS=" >> .env
64
+ echo "REGISTRATION_CODE=organization-registration-template" >> .env
65
+
66
+ # Create the .env.dev file
67
+ echo "VITE_FIREBASE_API_KEY=$api_key" > .env.dev
68
+ echo "VITE_FIREBASE_AUTH_DOMAIN=$auth_domain" >> .env.dev
69
+ echo "VITE_FIREBASE_PROJECT_ID=$project_id" >> .env.dev
70
+ echo "VITE_FIREBASE_STORAGE_BUCKET=$storage_bucket" >> .env.dev
71
+ echo "VITE_FIREBASE_MESSAGING_SENDER_ID=$messaging_sender_id" >> .env.dev
72
+ echo "VITE_FIREBASE_APP_ID=$app_id" >> .env.dev
73
+ echo "VITE_FIREBASE_MEASUREMENT_ID=$measurement_id" >> .env.dev
74
+ echo "VITE_FIREBASE_EMULATOR_AUTH=9099" >> .env.dev
75
+ echo "VITE_FIREBASE_EMULATOR_FIRESTORE=8080" >> .env.dev
76
+ echo "VITE_FIREBASE_EMULATOR_FUNCTIONS=5001" >> .env.dev
77
+ echo "REGISTRATION_CODE=organization-registration-template" >> .env.dev
@@ -0,0 +1,7 @@
1
+ STRIPE_API_KEY=
2
+ STRIPE_SECRET=
3
+ STRIPE_RETURN_URL=
4
+ OPENAI_API_KEY=
5
+ TWILIO_SID=
6
+ TWILIO_AUTH_TOKEN=
7
+ TWILIO_SYSTEM_NUMBER=
@@ -0,0 +1,7 @@
1
+ STRIPE_API_KEY=
2
+ STRIPE_SECRET=
3
+ STRIPE_RETURN_URL=
4
+ OPENAI_API_KEY=
5
+ TWILIO_SID=
6
+ TWILIO_AUTH_TOKEN=
7
+ TWILIO_SYSTEM_NUMBER=
@@ -0,0 +1,21 @@
1
+ const functions = require('firebase-functions')
2
+ const admin = require('firebase-admin')
3
+
4
+ admin.initializeApp()
5
+
6
+ const { onCall, HttpsError } = require('firebase-functions/v2/https')
7
+ const { logger } = require('firebase-functions/v2')
8
+ const { getFirestore } = require('firebase-admin/firestore')
9
+ const twilio = require('twilio')
10
+ const db = getFirestore()
11
+
12
+ module.exports = {
13
+ onCall,
14
+ HttpsError,
15
+ logger,
16
+ getFirestore,
17
+ functions,
18
+ admin,
19
+ twilio,
20
+ db,
21
+ }
@@ -0,0 +1,302 @@
1
+ const { onCall, HttpsError, logger, getFirestore, functions, admin, twilio, db } = require('./config.js')
2
+
3
+ const authToken = process.env.TWILIO_AUTH_TOKEN
4
+ const accountSid = process.env.TWILIO_SID
5
+ const systemNumber = process.env.TWILIO_SYSTEM_NUMBER
6
+
7
+ function formatPhoneNumber(phone) {
8
+ // Remove non-numeric characters from the phone number
9
+ const numericPhone = phone.replace(/\D/g, '')
10
+ // Return the formatted number
11
+ return `+1${numericPhone}`
12
+ }
13
+
14
+ exports.sendVerificationCode = functions.https.onCall(async (data, context) => {
15
+ const code = (Math.floor(Math.random() * 1000000) + 1000000).toString().substring(1)
16
+ const phone = formatPhoneNumber(data.phone)
17
+
18
+ try {
19
+ const client = twilio(accountSid, authToken)
20
+ await client.messages.create({
21
+ body: `Your verification code is: ${code}`,
22
+ to: phone, // the user's phone number
23
+ from: systemNumber, // your Twilio phone number from the configuration
24
+ })
25
+ }
26
+ catch (error) {
27
+ console.log(error)
28
+ return { success: false, error: 'Invalid Phone #' }
29
+ }
30
+
31
+ try {
32
+ // Use the formatted phone number as the document ID for Firestore
33
+ await db.collection('phone-auth').doc(phone).set({
34
+ phone,
35
+ code,
36
+ })
37
+ return phone
38
+ }
39
+ catch (error) {
40
+ return { success: false, error }
41
+ }
42
+ })
43
+
44
+ exports.verifyPhoneNumber = functions.https.onCall(async (data, context) => {
45
+ const phone = data.phone
46
+ const code = data.code
47
+
48
+ // Get the phone-auth document with the given phone number
49
+ const phoneDoc = await db.collection('phone-auth').doc(phone).get()
50
+
51
+ if (!phoneDoc.exists) {
52
+ return { success: false, error: 'Phone number not found.' }
53
+ }
54
+
55
+ const storedCode = phoneDoc.data().code
56
+
57
+ if (storedCode !== code) {
58
+ return { success: false, error: 'Invalid verification code.' }
59
+ }
60
+
61
+ // If the code matches, authenticate the user with Firebase Custom Auth
62
+ try {
63
+ // You would typically generate a UID based on the phone number or another system
64
+ const uid = phone
65
+
66
+ // Create a custom token (this can be used on the client to sign in)
67
+ const customToken = await admin.auth().createCustomToken(uid)
68
+
69
+ return { success: true, token: customToken }
70
+ }
71
+ catch (error) {
72
+ console.error('Error creating custom token:', error)
73
+ return { success: false, error: 'Failed to authenticate.' }
74
+ }
75
+ })
76
+
77
+ exports.initFirestore = functions.https.onCall(async (data, context) => {
78
+ // checks to see of the collections 'collection-data' and 'staged-users' exist if not will seed them with data
79
+ const collectionData = await db.collection('collection-data').get()
80
+ const stagedUsers = await db.collection('staged-users').get()
81
+ if (collectionData.empty) {
82
+ // create a document with the id of '-' and one called '-default-':
83
+ const admin = { assign: true, delete: true, read: true, write: true }
84
+ const editor = { assign: false, delete: true, read: true, write: true }
85
+ const writer = { assign: false, delete: false, read: true, write: true }
86
+ const user = { assign: false, delete: false, read: true, write: false }
87
+ await db.collection('collection-data').doc('-').set({ admin, editor, writer, user })
88
+ await db.collection('collection-data').doc('-default-').set({ admin, editor, writer, user })
89
+ }
90
+ if (stagedUsers.empty) {
91
+ const templateUser = {
92
+ docId: 'organization-registration-template',
93
+ isTemplate: true,
94
+ meta: {
95
+ name: 'Organization Registration Template',
96
+ },
97
+ subCreate: {
98
+ documentStructure: {
99
+ name: '',
100
+ },
101
+ dynamicDocumentField: 'name',
102
+ role: 'admin',
103
+ rootPath: 'organizations',
104
+ },
105
+ userId: '',
106
+ }
107
+ await db.collection('staged-users').doc('organization-registration-template').set(templateUser)
108
+ }
109
+ })
110
+
111
+ exports.removeNonRegisteredUser = functions.https.onCall(async (data, context) => {
112
+ if (data.uid === context.auth.uid) {
113
+ const stagedUser = await db.collection('staged-users').doc(data.docId).get()
114
+ if (stagedUser.exists) {
115
+ const stagedUserData = stagedUser.data()
116
+
117
+ const rolesExist = stagedUserData.roles && Object.keys(stagedUserData.roles).length !== 0
118
+ const specialPermissionsExist = stagedUserData.specialPermissions && Object.keys(stagedUserData.specialPermissions).length !== 0
119
+ const userIdExistsAndNotBlank = stagedUserData.userId && stagedUserData.userId !== ''
120
+
121
+ if (!rolesExist && !specialPermissionsExist && !userIdExistsAndNotBlank) {
122
+ await db.collection('staged-users').doc(data.docId).delete()
123
+ return { success: true, message: '' }
124
+ }
125
+ else {
126
+ let message = ''
127
+ if (rolesExist && specialPermissionsExist) {
128
+ message = 'Cannot delete because the non-registered user still has roles and special permissions assigned.'
129
+ }
130
+ else if (rolesExist) {
131
+ message = 'Cannot delete because the non-registered user still has roles assigned.'
132
+ }
133
+ else if (specialPermissionsExist) {
134
+ message = 'Cannot delete because the non-registered user still has special permissions assigned.'
135
+ }
136
+ else if (userIdExistsAndNotBlank) {
137
+ message = 'Cannot delete because the user is registered.'
138
+ }
139
+ return { success: false, message }
140
+ }
141
+ }
142
+ }
143
+ return { success: false, message: 'Non-registered user not found.' }
144
+ })
145
+
146
+ exports.currentUserRegister = functions.https.onCall(async (data, context) => {
147
+ if (data.uid === context.auth.uid) {
148
+ const stagedUser = await db.collection('staged-users').doc(data.registrationCode).get()
149
+ if (!stagedUser.exists) {
150
+ return { success: false, message: 'Registration code not found.' }
151
+ }
152
+ else {
153
+ const stagedUserData = await stagedUser.data()
154
+ let process = false
155
+ if (stagedUserData.isTemplate) {
156
+ process = true
157
+ }
158
+ if (!stagedUserData.isTemplate && stagedUserData.userId === '') {
159
+ process = true
160
+ }
161
+ if (!process) {
162
+ return { success: false, message: 'Registration code not valid.' }
163
+ }
164
+ const newRoles = stagedUserData.roles || {}
165
+ const currentUser = await db.collection('users').doc(data.uid).get()
166
+ const currentUserData = await currentUser.data()
167
+ const currentRoles = currentUserData.roles || {}
168
+ const currentUserCollectionPaths = currentUserData.collectionPaths || []
169
+ let newRole = {}
170
+ if (stagedUserData.subCreate && Object.keys(stagedUserData.subCreate).length !== 0 && stagedUserData.isTemplate) {
171
+ if (!data.dynamicDocumentFieldValue) {
172
+ return { success: false, message: 'Dynamic document field value is required.' }
173
+ }
174
+ const rootPath = stagedUserData.subCreate.rootPath
175
+ const newDoc = stagedUserData.subCreate.documentStructure
176
+ newDoc[stagedUserData.subCreate.dynamicDocumentField] = data.dynamicDocumentFieldValue
177
+ const addedDoc = await db.collection(rootPath).add(newDoc)
178
+ await db.collection(rootPath).doc(addedDoc.id).update({ docId: addedDoc.id })
179
+ newRole = { [`${rootPath}-${addedDoc.id}`]: { collectionPath: `${rootPath}-${addedDoc.id}`, role: stagedUserData.subCreate.role } }
180
+ }
181
+ const combinedRoles = { ...currentRoles, ...newRoles, ...newRole }
182
+ Object.values(combinedRoles).forEach((role) => {
183
+ if (!currentUserCollectionPaths.includes(role.collectionPath)) {
184
+ currentUserCollectionPaths.push(role.collectionPath)
185
+ }
186
+ })
187
+ await db.collection('staged-users').doc(currentUserData.stagedDocId).update({ roles: combinedRoles, collectionPaths: currentUserCollectionPaths })
188
+ if (!stagedUserData.isTemplate) {
189
+ await db.collection('staged-users').doc(data.registrationCode).delete()
190
+ }
191
+ return { success: true, message: '' }
192
+ }
193
+ }
194
+ })
195
+
196
+ exports.updateUser = functions.firestore.document('staged-users/{docId}').onUpdate(async (change, context) => {
197
+ const eventId = context.eventId
198
+ const eventRef = db.collection('events').doc(eventId)
199
+ const stagedDocId = context.params.docId
200
+ let newData = change.after.data()
201
+ const oldData = change.before.data()
202
+
203
+ const shouldProcess = await eventRef.get().then((eventDoc) => {
204
+ return !eventDoc.exists || !eventDoc.data().processed
205
+ })
206
+
207
+ if (!shouldProcess) {
208
+ return null
209
+ }
210
+
211
+ // Note: we can trust on newData.uid because we are checking in rules that it matches the auth.uid
212
+ if (newData.userId) {
213
+ const userRef = db.collection('users').doc(newData.userId)
214
+ await setUser(userRef, newData, oldData, stagedDocId)
215
+ await markProcessed(eventRef)
216
+ }
217
+ else {
218
+ if (newData.templateUserId !== oldData.templateUserId) {
219
+ // Check if templateUserId already exists in the staged-users collection
220
+ const stagedUserRef = db.collection('staged-users').doc(newData.templateUserId)
221
+ const doc = await stagedUserRef.get()
222
+
223
+ // If it exists, skip the creation process
224
+ if (doc.exists) {
225
+ return null
226
+ }
227
+
228
+ newData.isTemplate = false
229
+ const templateUserId = newData.templateUserId
230
+ newData.meta = newData.templateMeta
231
+ delete newData.templateMeta
232
+ delete newData.templateUserId
233
+ if (Object.prototype.hasOwnProperty.call(newData, 'subCreate') && Object.values(newData.subCreate).length > 0) {
234
+ const subCreate = newData.subCreate
235
+ delete newData.subCreate
236
+ const addedDoc = await db.collection(subCreate.rootPath).add({ [subCreate.dynamicDocumentField]: newData.dynamicDocumentFieldValue })
237
+ await db.collection(subCreate.rootPath).doc(addedDoc.id).update({ docId: addedDoc.id })
238
+ delete newData.dynamicDocumentFieldValue
239
+ const newRole = { [`${subCreate.rootPath}-${addedDoc.id}`]: { collectionPath: `${subCreate.rootPath}-${addedDoc.id}`, role: subCreate.role } }
240
+ if (Object.prototype.hasOwnProperty.call(newData, 'collectionPaths')) {
241
+ newData.collectionPaths.push(`${subCreate.rootPath}-${addedDoc.id}`)
242
+ }
243
+ else {
244
+ newData.collectionPaths = [`${subCreate.rootPath}-${addedDoc.id}`]
245
+ }
246
+ const newRoles = { ...newData.roles, ...newRole }
247
+ newData = { ...newData, roles: newRoles }
248
+ const stagedUserRef = db.collection('staged-users').doc(templateUserId)
249
+ await stagedUserRef.set({ ...newData, userId: templateUserId })
250
+ const userRef = db.collection('users').doc(templateUserId)
251
+ await setUser(userRef, newData, oldData, templateUserId)
252
+ await markProcessed(eventRef)
253
+ }
254
+ else {
255
+ const stagedUserRef = db.collection('staged-users').doc(templateUserId)
256
+ await stagedUserRef.set({ ...newData, userId: templateUserId })
257
+ const userRef = db.collection('users').doc(templateUserId)
258
+ await setUser(userRef, newData, oldData, templateUserId)
259
+ await markProcessed(eventRef)
260
+ }
261
+ }
262
+ }
263
+ await markProcessed(eventRef)
264
+ })
265
+
266
+ function setUser(userRef, newData, oldData, stagedDocId) {
267
+ // IT's OK If "users" doesn't match exactly matched "staged-users" because this is only preventing
268
+ // writing from outside the @edgdev/firebase functions, so discrepancies will be rare since
269
+ // the package will prevent before it gets this far.
270
+ return userRef.get().then((user) => {
271
+ let userUpdate = { meta: newData.meta, stagedDocId }
272
+
273
+ if (Object.prototype.hasOwnProperty.call(newData, 'roles')) {
274
+ userUpdate = { ...userUpdate, roles: newData.roles }
275
+ }
276
+ if (Object.prototype.hasOwnProperty.call(newData, 'specialPermissions')) {
277
+ userUpdate = { ...userUpdate, specialPermissions: newData.specialPermissions }
278
+ }
279
+
280
+ if (!oldData.userId) {
281
+ userUpdate = { ...userUpdate, userId: newData.uid }
282
+ }
283
+ if (!user.exists) {
284
+ return userRef.set(userUpdate)
285
+ }
286
+ else {
287
+ return userRef.update(userUpdate)
288
+ }
289
+ })
290
+ }
291
+
292
+ function shouldProcess(eventRef) {
293
+ return eventRef.get().then((eventDoc) => {
294
+ return !eventDoc.exists || !eventDoc.data().processed
295
+ })
296
+ }
297
+
298
+ function markProcessed(eventRef) {
299
+ return eventRef.set({ processed: true }).then(() => {
300
+ return null
301
+ })
302
+ }