From 740134a1273f49c9b24d61cc1fbcc81f3651ec9c Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Wed, 15 Sep 2021 12:44:24 +1000 Subject: [PATCH 1/2] add prettier husky hook on pre-commit --- .prettierignore | 2 ++ package-lock.json | 18 +++++++++--------- package.json | 11 ++++++++--- 3 files changed, 19 insertions(+), 12 deletions(-) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..6302288 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +node_modules/ +.yarn diff --git a/package-lock.json b/package-lock.json index a526a37..a0f788e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5828,9 +5828,9 @@ } }, "mri": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.6.tgz", - "integrity": "sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", "dev": true }, "ms": { @@ -6766,9 +6766,9 @@ "dev": true }, "prettier": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", - "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.0.tgz", + "integrity": "sha512-DsEPLY1dE5HF3BxCRBmD4uYZ+5DCbvatnolqTqcxEgKVZnL2kUfyu7b8pPQ5+hTBkdhU9SLUmK0/pHb07RE4WQ==", "dev": true }, "pretty-quick": { @@ -7534,9 +7534,9 @@ } }, "semver-regex": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.2.tgz", - "integrity": "sha512-bXWyL6EAKOJa81XG1OZ/Yyuq+oT0b2YLlxx7c+mrdYPaPbnj6WgVULXhinMIeZGufuUBu/eVRqXEhiv4imfwxA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.3.tgz", + "integrity": "sha512-Aqi54Mk9uYTjVexLnR67rTyBusmwd04cLkHy9hNvk3+G3nT2Oyg7E0l4XVbOaNwIvQ3hHeYxGcyEy+mKreyBFQ==", "dev": true }, "send": { diff --git a/package.json b/package.json index 3bdd035..d0c1ce6 100644 --- a/package.json +++ b/package.json @@ -34,15 +34,20 @@ "@types/node": "^16.6.1", "firebase-tools": "^9.18.0", "google-auth-library": "^7.0.0", - "husky": "^4.2.5", + "husky": "^4.3.8", "js-beautify": "^1.13.0", "mocha": "^9.0.0", "nodemon": "^2.0.12", - "prettier": "^2.1.1", - "pretty-quick": "^3.0.0", + "prettier": "^2.4.0", + "pretty-quick": "^3.1.1", "supertest": "^6.0.0", "ts-node": "^9.1.1", "tslint": "^6.1.0", "typescript": "^4.2.3" + }, + "husky": { + "hooks": { + "pre-commit": "pretty-quick --staged" + } } } From 737636d696ff474b10788bc206d90ae97e1316f6 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Wed, 15 Sep 2021 12:44:45 +1000 Subject: [PATCH 2/2] run prettier --- src/actionScripts/index.ts | 178 +++++++-------- src/constants/Collections.ts | 9 +- src/firebaseConfig.ts | 8 +- src/firestore/index.ts | 29 ++- src/functionBuilder/compiler/index.ts | 105 ++++----- src/functionBuilder/compiler/loader/index.ts | 205 +++++++++++------- .../compiler/loader/serialisers.ts | 48 ++-- src/functionBuilder/compiler/loader/types.ts | 85 ++++---- .../extensionsLib/slackMessage.ts | 86 ++++---- .../functions/src/derivatives/index.ts | 110 +++++----- .../functions/src/extensions/index.ts | 122 ++++++----- .../functions/src/functionConfig.ts | 193 ++++++++--------- src/functionBuilder/functions/src/index.ts | 2 +- .../functions/src/initialize/index.ts | 53 ++--- .../functions/src/utils/index.ts | 18 +- src/functionBuilder/index.ts | 22 +- src/functionBuilder/utils.ts | 14 +- src/hooks/createRowyApp.ts | 70 +++--- src/hooks/postcreate.ts | 25 +-- src/hooks/prebuild.ts | 70 +++--- src/hooks/utils.ts | 27 ++- src/index.ts | 84 ++++--- src/metadataInstance.ts | 16 +- src/middleware/auth.ts | 55 ++--- src/setup/ft2rowy.ts | 102 +++++---- src/setup/index.ts | 8 +- src/setup/region.ts | 10 +- src/setup/serviceAccountAccess.ts | 81 ++++--- src/setup/setOwnerRoles.ts | 50 +++-- src/setup/version.ts | 12 +- src/userManagement/deleteUser.ts | 24 +- src/userManagement/impersonateUser.ts | 30 +-- src/userManagement/index.ts | 8 +- src/userManagement/inviteUser.ts | 54 ++--- src/userManagement/setUserRoles.ts | 41 ++-- src/utils.ts | 45 ++-- 36 files changed, 1105 insertions(+), 994 deletions(-) diff --git a/src/actionScripts/index.ts b/src/actionScripts/index.ts index adb0b43..ac330d0 100644 --- a/src/actionScripts/index.ts +++ b/src/actionScripts/index.ts @@ -2,7 +2,7 @@ import * as functions from "firebase-functions"; import * as _ from "lodash"; import { db, auth } from "../firebaseConfig"; import * as admin from "firebase-admin"; -import { Request, Response } from 'express' +import { Request, Response } from "express"; //import utilFns from "./utils"; type ActionData = { @@ -32,95 +32,101 @@ const generateSchemaDocPath = (tablePath) => { }; const serverTimestamp = admin.firestore.FieldValue.serverTimestamp; -export const actionScript = async (req:Request,res:Response) => { - try { - const user = res.locals.user; - const { ref, actionParams, column, action, schemaDocPath }:ActionData = req.body; - const _schemaDocPath = - schemaDocPath ?? generateSchemaDocPath(ref.tablePath); - const [schemaDoc, rowQuery] = await Promise.all([ - db.doc(_schemaDocPath).get(), - db.doc(ref.path).get(), - ]); - const row = rowQuery.data(); - const schemaDocData = schemaDoc.data(); - if (!schemaDocData) { - return res.send({ - success: false, - message: "no schema found", - }); - } - const config = schemaDocData.columns[column.key].config; - const { script, requiredRoles, requiredFields } = config; - if (!requiredRoles || requiredRoles.length === 0) { - throw Error(`You need to specify at least one role to run this script`); - } - console.log(`requiredRoles: ${requiredRoles}`); - console.log(`userRoles: ${user.customClaims.roles}`) - if (!requiredRoles.some((role) => user.customClaims.roles.includes(role))) { - throw Error(`You don't have the required roles permissions`); - } +export const actionScript = async (req: Request, res: Response) => { + try { + const user = res.locals.user; + const { ref, actionParams, column, action, schemaDocPath }: ActionData = + req.body; + const _schemaDocPath = + schemaDocPath ?? generateSchemaDocPath(ref.tablePath); + const [schemaDoc, rowQuery] = await Promise.all([ + db.doc(_schemaDocPath).get(), + db.doc(ref.path).get(), + ]); + const row = rowQuery.data(); + const schemaDocData = schemaDoc.data(); + if (!schemaDocData) { + return res.send({ + success: false, + message: "no schema found", + }); + } + const config = schemaDocData.columns[column.key].config; + const { script, requiredRoles, requiredFields } = config; + if (!requiredRoles || requiredRoles.length === 0) { + throw Error(`You need to specify at least one role to run this script`); + } + console.log(`requiredRoles: ${requiredRoles}`); + console.log(`userRoles: ${user.customClaims.roles}`); + if (!requiredRoles.some((role) => user.customClaims.roles.includes(role))) { + throw Error(`You don't have the required roles permissions`); + } - const missingRequiredFields = requiredFields - ? requiredFields.reduce(missingFieldsReducer(row), []) - : []; - if (missingRequiredFields.length > 0) { - throw new Error( - `Missing required fields:${missingRequiredFields.join(", ")}` - ); - } - const result: { - message: string; - status: string; - success: boolean; - } = await eval( - `async({row,db, ref,auth,utilFns,actionParams,context})=>{${ - action === "undo" ? config["undo.script"] : script - }}` - )({ row, db, auth,// utilFns, - ref, actionParams, //context + const missingRequiredFields = requiredFields + ? requiredFields.reduce(missingFieldsReducer(row), []) + : []; + if (missingRequiredFields.length > 0) { + throw new Error( + `Missing required fields:${missingRequiredFields.join(", ")}` + ); + } + const result: { + message: string; + status: string; + success: boolean; + } = await eval( + `async({row,db, ref,auth,utilFns,actionParams,context})=>{${ + action === "undo" ? config["undo.script"] : script + }}` + )({ + row, + db, + auth, // utilFns, + ref, + actionParams, //context + }); + if (result.success || result.status) { + const cellValue = { + redo: result.success ? config["redo.enabled"] : true, + status: result.status, + completedAt: serverTimestamp(), + ranBy: user.email, + undo: config["undo.enabled"], + }; + try { + const userDoc = await db + .collection("/_rowy_/userManagement/users") + .doc(user.uid) + .get(); + const userData = userDoc?.get("user"); + await db.doc(ref.path).update({ + [column.key]: cellValue, + updatedBy: userData + ? { + ...userData, + ...user, + timestamp: new Date(), + } + : null, }); - if (result.success || result.status) { - const cellValue = { - redo: result.success ? config["redo.enabled"]:true, - status: result.status, - completedAt: serverTimestamp(), - ranBy: user.email, - undo: config["undo.enabled"], - }; - try { - const userDoc = await db - .collection("/_rowy_/userManagement/users") - .doc(user.uid) - .get(); - const userData = userDoc?.get("user"); - await db.doc(ref.path).update({ - [column.key]: cellValue, - updatedBy: userData - ? { - ...userData, - ...user, - timestamp: new Date(), - } - : null, - }); - } catch (error) { - // if actionScript code deletes the row, it will throw an error when updating the cell - } + } catch (error) { + // if actionScript code deletes the row, it will throw an error when updating the cell + } - return res.send({ - ...result,cellValue - }); - }else return res.send({ - success: false, - message: result.message, - }); - } catch (error:any) { + return res.send({ + ...result, + cellValue, + }); + } else return res.send({ success: false, - error, - message: error.message, + message: result.message, }); - } + } catch (error: any) { + return res.send({ + success: false, + error, + message: error.message, + }); } - +}; diff --git a/src/constants/Collections.ts b/src/constants/Collections.ts index e492721..27c7384 100644 --- a/src/constants/Collections.ts +++ b/src/constants/Collections.ts @@ -1,7 +1,8 @@ -export const rowyUsers = '_rowy_/userManagement/users' -export const rowyUsersImpersonationLogs = '_rowy_/userManagement/impersonationLogs' +export const rowyUsers = "_rowy_/userManagement/users"; +export const rowyUsersImpersonationLogs = + "_rowy_/userManagement/impersonationLogs"; export default { rowyUsers, - rowyUsersImpersonationLogs -} + rowyUsersImpersonationLogs, +}; diff --git a/src/firebaseConfig.ts b/src/firebaseConfig.ts index c2813b5..871248f 100644 --- a/src/firebaseConfig.ts +++ b/src/firebaseConfig.ts @@ -1,11 +1,11 @@ // Initialize Firebase Admin import * as admin from "firebase-admin"; -const credential = process.env.DEV ? -admin.credential.cert(require(`../firebase-adminsdk.json`)) -:admin.credential.applicationDefault() +const credential = process.env.DEV + ? admin.credential.cert(require(`../firebase-adminsdk.json`)) + : admin.credential.applicationDefault(); admin.initializeApp({ - credential + credential, }); const db = admin.firestore(); const auth = admin.auth(); diff --git a/src/firestore/index.ts b/src/firestore/index.ts index e7ac789..ca93631 100644 --- a/src/firestore/index.ts +++ b/src/firestore/index.ts @@ -1,21 +1,20 @@ - -import { db, admin } from '../firebaseConfig' -import { Request, Response } from 'express'; -export const listCollections =async (req: Request) => { +import { db, admin } from "../firebaseConfig"; +import { Request, Response } from "express"; +export const listCollections = async (req: Request) => { const { path } = req.query; if (path) { - const collections = await db.doc(decodeURIComponent(path as string)).listCollections() - return collections.map(collection => collection.id) - + const collections = await db + .doc(decodeURIComponent(path as string)) + .listCollections(); + return collections.map((collection) => collection.id); } else { - const collections = await db.listCollections() - return collections.map(collection => collection.id) + const collections = await db.listCollections(); + return collections.map((collection) => collection.id); } -} - +}; export const getFirestoreRules = async () => { - const securityRules = admin.securityRules() - const firestoreRules = await securityRules.getFirestoreRuleset() - return firestoreRules -} + const securityRules = admin.securityRules(); + const firestoreRules = await securityRules.getFirestoreRuleset(); + return firestoreRules; +}; diff --git a/src/functionBuilder/compiler/index.ts b/src/functionBuilder/compiler/index.ts index 728e380..55ba759 100644 --- a/src/functionBuilder/compiler/index.ts +++ b/src/functionBuilder/compiler/index.ts @@ -10,63 +10,64 @@ export default async function generateConfig( user: admin.auth.UserRecord, streamLogger ) { - return await generateConfigOfTriggerPath( - triggerPath, - streamLogger - ).then(async () => { - await streamLogger.info(`generateConfigFromTableSchema done`); - const configFile = fs.readFileSync( - path.resolve(__dirname, "../functions/src/functionConfig.ts"), - "utf-8" - ); - await streamLogger.info(`configFile: ${JSON.stringify(configFile)}`); - const requiredDependencies = configFile.match( - /(?<=(require\(("|'))).*?(?=("|')\))/g - ); - if (requiredDependencies) { - const packgesAdded = await addPackages( - requiredDependencies.map((p: any) => ({ name: p })), - user, - streamLogger + return await generateConfigOfTriggerPath(triggerPath, streamLogger).then( + async () => { + await streamLogger.info(`generateConfigFromTableSchema done`); + const configFile = fs.readFileSync( + path.resolve(__dirname, "../functions/src/functionConfig.ts"), + "utf-8" ); - if (!packgesAdded) { - return false; + await streamLogger.info(`configFile: ${JSON.stringify(configFile)}`); + const requiredDependencies = configFile.match( + /(?<=(require\(("|'))).*?(?=("|')\))/g + ); + if (requiredDependencies) { + const packgesAdded = await addPackages( + requiredDependencies.map((p: any) => ({ name: p })), + user, + streamLogger + ); + if (!packgesAdded) { + return false; + } } - } - await streamLogger.info( - `requiredDependencies: ${JSON.stringify(requiredDependencies)}` - ); + await streamLogger.info( + `requiredDependencies: ${JSON.stringify(requiredDependencies)}` + ); - const isFunctionConfigValid = await asyncExecute( - "cd build/functionBuilder/functions/src; tsc functionConfig.ts", - commandErrorHandler( - { - user, - functionConfigTs: configFile, - description: `Invalid compiled functionConfig.ts`, - }, - streamLogger - ) - ); - await streamLogger.info( - `isFunctionConfigValid: ${JSON.stringify(isFunctionConfigValid)}` - ); - if (!isFunctionConfigValid) { - return false; - } + const isFunctionConfigValid = await asyncExecute( + "cd build/functionBuilder/functions/src; tsc functionConfig.ts", + commandErrorHandler( + { + user, + functionConfigTs: configFile, + description: `Invalid compiled functionConfig.ts`, + }, + streamLogger + ) + ); + await streamLogger.info( + `isFunctionConfigValid: ${JSON.stringify(isFunctionConfigValid)}` + ); + if (!isFunctionConfigValid) { + return false; + } - const { extensionsConfig } = require("../functions/src/functionConfig.js"); - const requiredExtensions = extensionsConfig.map((s: any) => s.type); - await streamLogger.info( - `requiredExtensions: ${JSON.stringify(requiredExtensions)}` - ); + const { + extensionsConfig, + } = require("../functions/src/functionConfig.js"); + const requiredExtensions = extensionsConfig.map((s: any) => s.type); + await streamLogger.info( + `requiredExtensions: ${JSON.stringify(requiredExtensions)}` + ); - for (const lib of requiredExtensions) { - const success = await addExtensionLib(lib, user, streamLogger); - if (!success) { - return false; + for (const lib of requiredExtensions) { + const success = await addExtensionLib(lib, user, streamLogger); + if (!success) { + return false; + } } + return true; } - return true; - }); + ); } diff --git a/src/functionBuilder/compiler/loader/index.ts b/src/functionBuilder/compiler/loader/index.ts index 29de621..db458f5 100644 --- a/src/functionBuilder/compiler/loader/index.ts +++ b/src/functionBuilder/compiler/loader/index.ts @@ -1,12 +1,17 @@ import { db } from "../../../firebaseConfig"; -import { serialiseDerivativeColumns, serialiseDefaultValueColumns, serialiseDocumentSelectColumns, serialiseExtension } from "./serialisers"; -import { TriggerPathType,TableConfig } from "./types"; +import { + serialiseDerivativeColumns, + serialiseDefaultValueColumns, + serialiseDocumentSelectColumns, + serialiseExtension, +} from "./serialisers"; +import { TriggerPathType, TableConfig } from "./types"; const fs = require("fs"); const beautify = require("js-beautify").js; const getConfigFromTableSchema = async ( schemaDocPath: string, - streamLogger + streamLogger ) => { await streamLogger.info("getting schema..."); const schemaDoc = await db.doc(schemaDocPath).get(); @@ -16,39 +21,42 @@ const getConfigFromTableSchema = async ( const derivativeColumns = Object.values(schemaData.columns).filter( (col: any) => col.type === "DERIVATIVE" ); - const defaultValueColumns = Object.values( - schemaData.columns - ).filter((col: any) => Boolean(col.config?.defaultValue)); - + const defaultValueColumns = Object.values(schemaData.columns).filter( + (col: any) => Boolean(col.config?.defaultValue) + ); + const documentSelectColumns = Object.values(schemaData.columns).filter( (col: any) => col.type === "DOCUMENT_SELECT" && col.config?.trackedFields ); - const extensions = schemaData.extensionObjects + const extensions = schemaData.extensionObjects; // generate field types from table meta data - const fieldTypes = - Object.keys(schemaData.columns).reduce((acc, cur) => { - const field = schemaData.columns[cur]; - let fieldType = field.type; - if (fieldType === "DERIVATIVE") { - fieldType = field.config.renderFieldType; - } - return { - [cur]: fieldType, - ...acc, - }; - }, {}) - + const fieldTypes = Object.keys(schemaData.columns).reduce((acc, cur) => { + const field = schemaData.columns[cur]; + let fieldType = field.type; + if (fieldType === "DERIVATIVE") { + fieldType = field.config.renderFieldType; + } + return { + [cur]: fieldType, + ...acc, + }; + }, {}); + const config = { derivativeColumns, defaultValueColumns, documentSelectColumns, fieldTypes, extensions, - } - await Promise.all(Object.keys(config).map(async (key) =>streamLogger.info(`${key}: ${JSON.stringify(config[key])}`))) - return config - } catch (error:any) { + }; + await Promise.all( + Object.keys(config).map(async (key) => + streamLogger.info(`${key}: ${JSON.stringify(config[key])}`) + ) + ); + return config; + } catch (error: any) { streamLogger.error(error.message); return false; } @@ -70,59 +78,83 @@ const getConfigFromTableSchema = async ( const getTriggerType = (triggerPath: string): TriggerPathType => { const triggerPathParts = triggerPath.split("/"); // collectionType only has one variable - const numberOfVariables = triggerPathParts.filter(part=>part.startsWith("{")).length; + const numberOfVariables = triggerPathParts.filter((part) => + part.startsWith("{") + ).length; if (numberOfVariables === 1) return "collection"; // subCollection type has variables in even parts only - const numberOfVariablesInEvenParts = triggerPathParts.filter((part, index) => index % 2 === 0 && part.startsWith("{")).length; - const numberOfVariablesInOddParts = triggerPathParts.filter((part, index) => index % 2 !== 0 && part.startsWith("{")).length; - if (numberOfVariablesInOddParts ===0 && numberOfVariablesInEvenParts > 1) return "subCollection"; + const numberOfVariablesInEvenParts = triggerPathParts.filter( + (part, index) => index % 2 === 0 && part.startsWith("{") + ).length; + const numberOfVariablesInOddParts = triggerPathParts.filter( + (part, index) => index % 2 !== 0 && part.startsWith("{") + ).length; + if (numberOfVariablesInOddParts === 0 && numberOfVariablesInEvenParts > 1) + return "subCollection"; else return "collectionGroup"; -} - +}; export const functionNamer = (triggerPath: string) => { - const triggerPathParts = triggerPath.split("/") - switch (getTriggerType(triggerPath)) { - case "collection": - return triggerPathParts.filter(part=>!part.startsWith("{")).join("-") - default: - return "" - } -} - + const triggerPathParts = triggerPath.split("/"); + switch (getTriggerType(triggerPath)) { + case "collection": + return triggerPathParts.filter((part) => !part.startsWith("{")).join("-"); + default: + return ""; + } +}; -const combineConfigs = (configs: any[]) => configs.reduce((acc, cur:TableConfig) => { - const {derivativeColumns, defaultValueColumns, documentSelectColumns, fieldTypes, extensions} = cur; - return { - derivativeColumns: [...acc.derivativeColumns, ...derivativeColumns], - defaultValueColumns: [...acc.defaultValueColumns, ...defaultValueColumns], - documentSelectColumns: [...acc.documentSelectColumns, ...documentSelectColumns], - fieldTypes: {...acc.fieldTypes, ...fieldTypes}, - extensions: extensions?[...acc.extensions, ...extensions]:acc.extensions, +const combineConfigs = (configs: any[]) => + configs.reduce( + (acc, cur: TableConfig) => { + const { + derivativeColumns, + defaultValueColumns, + documentSelectColumns, + fieldTypes, + extensions, + } = cur; + return { + derivativeColumns: [...acc.derivativeColumns, ...derivativeColumns], + defaultValueColumns: [ + ...acc.defaultValueColumns, + ...defaultValueColumns, + ], + documentSelectColumns: [ + ...acc.documentSelectColumns, + ...documentSelectColumns, + ], + fieldTypes: { ...acc.fieldTypes, ...fieldTypes }, + extensions: extensions + ? [...acc.extensions, ...extensions] + : acc.extensions, + }; + }, + { + derivativeColumns: [], + defaultValueColumns: [], + documentSelectColumns: [], + fieldTypes: {}, + extensions: [], } - },{ - derivativeColumns: [], - defaultValueColumns: [], - documentSelectColumns: [], - fieldTypes: {}, - extensions: [], - }) - - - -const generateFile = async (triggerPath,configData) => { - - + ); - const {derivativeColumns, defaultValueColumns, documentSelectColumns, fieldTypes, extensions} = configData; +const generateFile = async (triggerPath, configData) => { + const { + derivativeColumns, + defaultValueColumns, + documentSelectColumns, + fieldTypes, + extensions, + } = configData; const data = { - fieldTypes:JSON.stringify(fieldTypes), + fieldTypes: JSON.stringify(fieldTypes), triggerPath: JSON.stringify(triggerPath), functionName: JSON.stringify(functionNamer(triggerPath)), derivativesConfig: serialiseDerivativeColumns(derivativeColumns), defaultValueConfig: serialiseDefaultValueColumns(defaultValueColumns), documentSelectConfig: serialiseDocumentSelectColumns(documentSelectColumns), - extensionsConfig:serialiseExtension(extensions), + extensionsConfig: serialiseExtension(extensions), }; const fileData = Object.keys(data).reduce((acc, currKey) => { return `${acc}\nexport const ${currKey} = ${data[currKey]}`; @@ -132,27 +164,44 @@ const generateFile = async (triggerPath,configData) => { path.resolve(__dirname, "../../functions/src/functionConfig.ts"), beautify(fileData, { indent_size: 2 }) ); -} +}; -export const generateConfigOfTriggerPath = async (triggerPath:string,streamLogger) => { +export const generateConfigOfTriggerPath = async ( + triggerPath: string, + streamLogger +) => { const triggerPathType = getTriggerType(triggerPath); - let tableSchemaPaths = [] + let tableSchemaPaths = []; const settingsDoc = await db.doc("_rowy_/settings").get(); - const {tables} = settingsDoc.data(); + const { tables } = settingsDoc.data(); switch (triggerPathType) { case "collection": const collection = triggerPath.split("/")[0]; - const collectionTables = tables.filter((table: any) => table.collection === collection); - tableSchemaPaths = collectionTables.map((table: any) => `/_rowy_/settings/schema/${table.id??table.collection}`); + const collectionTables = tables.filter( + (table: any) => table.collection === collection + ); + tableSchemaPaths = collectionTables.map( + (table: any) => + `/_rowy_/settings/schema/${table.id ?? table.collection}` + ); break; default: break; } - const configs = (await Promise.all(tableSchemaPaths.map(path=> getConfigFromTableSchema(path,streamLogger)))).filter(i=>i !== false) - const combinedConfig = combineConfigs(configs) - await db.doc(`_rowy_/settings/functions/${functionNamer(triggerPath)}`).set({ - config:combinedConfig, - updatedAt: new Date(), - },{merge:true}); - return generateFile(triggerPath,combinedConfig) -} \ No newline at end of file + const configs = ( + await Promise.all( + tableSchemaPaths.map((path) => + getConfigFromTableSchema(path, streamLogger) + ) + ) + ).filter((i) => i !== false); + const combinedConfig = combineConfigs(configs); + await db.doc(`_rowy_/settings/functions/${functionNamer(triggerPath)}`).set( + { + config: combinedConfig, + updatedAt: new Date(), + }, + { merge: true } + ); + return generateFile(triggerPath, combinedConfig); +}; diff --git a/src/functionBuilder/compiler/loader/serialisers.ts b/src/functionBuilder/compiler/loader/serialisers.ts index fb09367..1eec94e 100644 --- a/src/functionBuilder/compiler/loader/serialisers.ts +++ b/src/functionBuilder/compiler/loader/serialisers.ts @@ -1,11 +1,11 @@ -import {IExtension} from './types' -/* Convert extension objects into a single readable string */ +import { IExtension } from "./types"; +/* Convert extension objects into a single readable string */ export const serialiseExtension = (extensions: IExtension[]): string => - "[" + - extensions - .filter((extension) => extension.active) - .map( - (extension) => `{ + "[" + + extensions + .filter((extension) => extension.active) + .map( + (extension) => `{ name: "${extension.name}", type: "${extension.type}", triggers: [${extension.triggers @@ -21,13 +21,13 @@ export const serialiseExtension = (extensions: IExtension[]): string => .replace(/^.*:\s*\w*Body\s*=/, "") .replace(/\s*;\s*$/, "")} }` - ) - .join(",") + - "]"; + ) + .join(",") + + "]"; /* convert derivative columns into a readable string */ -export const serialiseDerivativeColumns = (derivativeColumns: any[]): string => `[${derivativeColumns.reduce( - (acc, currColumn: any) => { +export const serialiseDerivativeColumns = (derivativeColumns: any[]): string => + `[${derivativeColumns.reduce((acc, currColumn: any) => { if ( !currColumn.config.listenerFields || currColumn.config.listenerFields.length === 0 @@ -46,12 +46,12 @@ export const serialiseDerivativeColumns = (derivativeColumns: any[]): string => }},\nlistenerFields:[${currColumn.config.listenerFields .map((fieldKey: string) => `"${fieldKey}"`) .join(",\n")}]},\n`; - }, - "" -)}]` + }, "")}]`; -export const serialiseDefaultValueColumns = (defaultValueColumns: any[]): string => `[${defaultValueColumns.reduce( - (acc, currColumn: any) => { +export const serialiseDefaultValueColumns = ( + defaultValueColumns: any[] +): string => + `[${defaultValueColumns.reduce((acc, currColumn: any) => { if (currColumn.config.defaultValue.type === "static") { return `${acc}{\nfieldName:'${currColumn.key}', type:"${currColumn.config.defaultValue.type}", @@ -71,17 +71,15 @@ export const serialiseDefaultValueColumns = (defaultValueColumns: any[]): string type:"${currColumn.config.defaultValue.type}" },\n`; } - }, - "" -)}]` + }, "")}]`; -export const serialiseDocumentSelectColumns = (documentSelectColumns: any[]): string => `[${documentSelectColumns.reduce( - (acc, currColumn: any) => { +export const serialiseDocumentSelectColumns = ( + documentSelectColumns: any[] +): string => + `[${documentSelectColumns.reduce((acc, currColumn: any) => { return `${acc}{\nfieldName:'${ currColumn.key }',\ntrackedFields:[${currColumn.config.trackedFields .map((fieldKey: string) => `"${fieldKey}"`) .join(",\n")}]},\n`; - }, - "" -)}]`; \ No newline at end of file + }, "")}]`; diff --git a/src/functionBuilder/compiler/loader/types.ts b/src/functionBuilder/compiler/loader/types.ts index 57ce88a..0626b9c 100644 --- a/src/functionBuilder/compiler/loader/types.ts +++ b/src/functionBuilder/compiler/loader/types.ts @@ -1,43 +1,44 @@ +export type IExtensionType = + | "task" + | "docSync" + | "historySnapshot" + | "algoliaIndex" + | "meiliIndex" + | "bigqueryIndex" + | "slackMessage" + | "sendgridEmail" + | "apiCall" + | "twilioMessage"; - - export type IExtensionType = - | "task" - | "docSync" - | "historySnapshot" - | "algoliaIndex" - | "meiliIndex" - | "bigqueryIndex" - | "slackMessage" - | "sendgridEmail" - | "apiCall" - | "twilioMessage"; - - export type IExtensionTrigger = "create" | "update" | "delete"; - - export interface IExtensionEditor { - displayName: string; - photoURL: string; - lastUpdate: number; - } - - export interface IExtension { - // rowy meta fields - name: string; - active: boolean; - lastEditor: IExtensionEditor; - - // ft build fields - triggers: IExtensionTrigger[]; - type: IExtensionType; - requiredFields: string[]; - extensionBody: string; - conditions: string; - } - export type TriggerPathType = "collection" | "collectionGroup" | "subCollection" - export interface TableConfig{ - derivativeColumns: any[]; - defaultValueColumns: any[]; - documentSelectColumns: any[]; - fieldTypes: any; - extensions: IExtension[]; - } \ No newline at end of file +export type IExtensionTrigger = "create" | "update" | "delete"; + +export interface IExtensionEditor { + displayName: string; + photoURL: string; + lastUpdate: number; +} + +export interface IExtension { + // rowy meta fields + name: string; + active: boolean; + lastEditor: IExtensionEditor; + + // ft build fields + triggers: IExtensionTrigger[]; + type: IExtensionType; + requiredFields: string[]; + extensionBody: string; + conditions: string; +} +export type TriggerPathType = + | "collection" + | "collectionGroup" + | "subCollection"; +export interface TableConfig { + derivativeColumns: any[]; + defaultValueColumns: any[]; + documentSelectColumns: any[]; + fieldTypes: any; + extensions: IExtension[]; +} diff --git a/src/functionBuilder/extensionsLib/slackMessage.ts b/src/functionBuilder/extensionsLib/slackMessage.ts index 70fdca2..a795d69 100644 --- a/src/functionBuilder/extensionsLib/slackMessage.ts +++ b/src/functionBuilder/extensionsLib/slackMessage.ts @@ -13,54 +13,58 @@ const initSlack = async () => { return new WebClient(token); }; -const messageByChannel = (slackClient) => async ({ - text, - channel, - blocks, - attachments, -}: { - channel: string; - text: string; - blocks: any[]; - attachments: any[]; -}) => - await slackClient.chat.postMessage({ +const messageByChannel = + (slackClient) => + async ({ text, channel, blocks, attachments, - }); + }: { + channel: string; + text: string; + blocks: any[]; + attachments: any[]; + }) => + await slackClient.chat.postMessage({ + text, + channel, + blocks, + attachments, + }); -const messageByEmail = (slackClient) => async ({ - email, - text, - blocks, - attachments, -}: { - email: string; - text: string; - blocks: any[]; - attachments: any[]; -}) => { - try { - const user = await slackClient.users.lookupByEmail({ email }); - if (user.ok) { - const channel = user.user.id; - return await messageByChannel(slackClient)({ - text, - blocks, - attachments, - channel, - }); - } else { +const messageByEmail = + (slackClient) => + async ({ + email, + text, + blocks, + attachments, + }: { + email: string; + text: string; + blocks: any[]; + attachments: any[]; + }) => { + try { + const user = await slackClient.users.lookupByEmail({ email }); + if (user.ok) { + const channel = user.user.id; + return await messageByChannel(slackClient)({ + text, + blocks, + attachments, + channel, + }); + } else { + return await false; + } + } catch (error) { + console.log(`${error} maybe${email} is not on slack`); + console.log(`${error}`); return await false; } - } catch (error) { - console.log(`${error} maybe${email} is not on slack`); - console.log(`${error}`); - return await false; - } -}; + }; const slackMessage = async (data) => { const slackClient = await initSlack(); diff --git a/src/functionBuilder/functions/src/derivatives/index.ts b/src/functionBuilder/functions/src/derivatives/index.ts index 29a66c9..bfb50fc 100644 --- a/src/functionBuilder/functions/src/derivatives/index.ts +++ b/src/functionBuilder/functions/src/derivatives/index.ts @@ -3,61 +3,63 @@ import * as admin from "firebase-admin"; import { db, auth, storage } from "../firebaseConfig"; import utilFns from "../utils"; -const derivative = ( - functionConfig: { - fieldName: string; - listenerFields: string[]; - evaluate: (props: { - row: any; - ref: FirebaseFirestore.DocumentReference; - db: FirebaseFirestore.Firestore; - auth: admin.auth.Auth; - storage: admin.storage.Storage; - utilFns: any; - }) => any; - }[] -) => async (change: functions.Change) => { - try { - const row = change.after?.data(); - const ref = change.after ? change.after.ref : change.before.ref; - const update = await functionConfig.reduce( - async (accUpdates: any, currDerivative) => { - const shouldEval = utilFns.hasChanged(change)([ - ...currDerivative.listenerFields, - "_ft_forcedUpdateAt", - ]); - if (shouldEval) { - try { - const newValue = await currDerivative.evaluate({ - row, - ref, - db, - auth, - storage, - utilFns, - }); - if ( - newValue !== undefined && - newValue !== row[currDerivative.fieldName] - ) { - return { - ...(await accUpdates), - [currDerivative.fieldName]: newValue, - }; +const derivative = + ( + functionConfig: { + fieldName: string; + listenerFields: string[]; + evaluate: (props: { + row: any; + ref: FirebaseFirestore.DocumentReference; + db: FirebaseFirestore.Firestore; + auth: admin.auth.Auth; + storage: admin.storage.Storage; + utilFns: any; + }) => any; + }[] + ) => + async (change: functions.Change) => { + try { + const row = change.after?.data(); + const ref = change.after ? change.after.ref : change.before.ref; + const update = await functionConfig.reduce( + async (accUpdates: any, currDerivative) => { + const shouldEval = utilFns.hasChanged(change)([ + ...currDerivative.listenerFields, + "_ft_forcedUpdateAt", + ]); + if (shouldEval) { + try { + const newValue = await currDerivative.evaluate({ + row, + ref, + db, + auth, + storage, + utilFns, + }); + if ( + newValue !== undefined && + newValue !== row[currDerivative.fieldName] + ) { + return { + ...(await accUpdates), + [currDerivative.fieldName]: newValue, + }; + } + } catch (error) { + console.log(error); } - } catch (error) { - console.log(error); } - } - return await accUpdates; - }, - {} - ); - return update; - } catch (error) { - console.log(`Derivatives Error`, error); - return {}; - } -}; + return await accUpdates; + }, + {} + ); + return update; + } catch (error) { + console.log(`Derivatives Error`, error); + return {}; + } + }; export default derivative; diff --git a/src/functionBuilder/functions/src/extensions/index.ts b/src/functionBuilder/functions/src/extensions/index.ts index 0b1642b..d060502 100644 --- a/src/functionBuilder/functions/src/extensions/index.ts +++ b/src/functionBuilder/functions/src/extensions/index.ts @@ -2,67 +2,69 @@ import * as functions from "firebase-functions"; import utilFns, { hasRequiredFields, getTriggerType } from "../utils"; import { db, auth, storage } from "../firebaseConfig"; -const extension = (extensionConfig, fieldTypes) => async ( - change: functions.Change, - context: functions.EventContext -) => { - const beforeData = change.before?.data(); - const afterData = change.after?.data(); - const ref = change.after ? change.after.ref : change.before.ref; - const triggerType = getTriggerType(change); - try { - const { - name, - type, - triggers, - conditions, - requiredFields, - extensionBody, - } = extensionConfig; - const extensionContext = { - row: triggerType === "delete" ? beforeData : afterData, - ref, - db, - auth, - change, - triggerType, - extensionConfig, - utilFns, - fieldTypes, - storage, - }; - if (!triggers.includes(triggerType)) return false; //check if trigger type is included in the extension - if ( - triggerType !== "delete" && - requiredFields && - requiredFields.length !== 0 && - !hasRequiredFields(requiredFields, afterData) - ) { - console.log("requiredFields are ", requiredFields, "type is", type); - return false; // check if it hase required fields for the extension to run - } - const dontRun = conditions - ? !(typeof conditions === "function" - ? await conditions(extensionContext) - : conditions) - : false; // +const extension = + (extensionConfig, fieldTypes) => + async ( + change: functions.Change, + context: functions.EventContext + ) => { + const beforeData = change.before?.data(); + const afterData = change.after?.data(); + const ref = change.after ? change.after.ref : change.before.ref; + const triggerType = getTriggerType(change); + try { + const { + name, + type, + triggers, + conditions, + requiredFields, + extensionBody, + } = extensionConfig; + const extensionContext = { + row: triggerType === "delete" ? beforeData : afterData, + ref, + db, + auth, + change, + triggerType, + extensionConfig, + utilFns, + fieldTypes, + storage, + }; + if (!triggers.includes(triggerType)) return false; //check if trigger type is included in the extension + if ( + triggerType !== "delete" && + requiredFields && + requiredFields.length !== 0 && + !hasRequiredFields(requiredFields, afterData) + ) { + console.log("requiredFields are ", requiredFields, "type is", type); + return false; // check if it hase required fields for the extension to run + } + const dontRun = conditions + ? !(typeof conditions === "function" + ? await conditions(extensionContext) + : conditions) + : false; // - console.log(`name: "${name}", type: "${type}", dontRun: ${dontRun}`); + console.log(`name: "${name}", type: "${type}", dontRun: ${dontRun}`); - if (dontRun) return false; - const extensionData = await extensionBody(extensionContext); - console.log(`extensionData: ${JSON.stringify(extensionData)}`); - const extensionFn = require(`./${type}`).default; - await extensionFn(extensionData, extensionContext); - return true; - } catch (err) { - const { name, type } = extensionConfig; - console.log( - `error in ${name} extension of type ${type}, on ${context.eventType} in Doc ${context.resource.name}` - ); - console.error(err); - return Promise.reject(err); - } -}; + if (dontRun) return false; + const extensionData = await extensionBody(extensionContext); + console.log(`extensionData: ${JSON.stringify(extensionData)}`); + const extensionFn = require(`./${type}`).default; + await extensionFn(extensionData, extensionContext); + return true; + } catch (err) { + const { name, type } = extensionConfig; + console.log( + `error in ${name} extension of type ${type}, on ${context.eventType} in Doc ${context.resource.name}` + ); + console.error(err); + return Promise.reject(err); + } + }; export default extension; diff --git a/src/functionBuilder/functions/src/functionConfig.ts b/src/functionBuilder/functions/src/functionConfig.ts index fdb8ae7..234d684 100644 --- a/src/functionBuilder/functions/src/functionConfig.ts +++ b/src/functionBuilder/functions/src/functionConfig.ts @@ -1,118 +1,99 @@ export const fieldTypes = { - "email": "EMAIL", - "action": "ACTION", - "shortText": "SIMPLE_TEXT", - "connectTable": "DOCUMENT_SELECT", - "url": "URL", - "phone": "PHONE_NUMBER", - "connectSerivce": "SERVICE_SELECT", - "longText": "LONG_TEXT", - "subtable": "SUB_TABLE", - "dateTime": "DATE_TIME", - "singleSelect": "SINGLE_SELECT", - "image": "IMAGE", - "number": "NUMBER", - "color": "COLOR", - "slider": "SLIDER", - "date": "DATE", - "derivative": "SIMPLE_TEXT", - "json": "JSON", - "percentage": "PERCENTAGE", - "status": "STATUS", - "code": "CODE", - "multiSelect": "MULTI_SELECT", - "rating": "RATING", - "toggle": "CHECK_BOX", - "file": "FILE", - "id": "ID", - "duration": "DURATION", - "_updatedBy": "USER", - "table2Only": "SIMPLE_TEXT", - "subTableTest": "SUB_TABLE" -} -export const triggerPath = "demoAllFieldTypes/{docId}" -export const functionName = "demoAllFieldTypes" -export const derivativesConfig = [{ - fieldName: 'derivative', - evaluate: async ({ - row, - ref, - db, - auth, - storage, - utilFns - }) => { - if (row.toggle) return "toggle is on" - if (row.toggle === false) return "toggle is off" - else return "toggle is not set" + email: "EMAIL", + action: "ACTION", + shortText: "SIMPLE_TEXT", + connectTable: "DOCUMENT_SELECT", + url: "URL", + phone: "PHONE_NUMBER", + connectSerivce: "SERVICE_SELECT", + longText: "LONG_TEXT", + subtable: "SUB_TABLE", + dateTime: "DATE_TIME", + singleSelect: "SINGLE_SELECT", + image: "IMAGE", + number: "NUMBER", + color: "COLOR", + slider: "SLIDER", + date: "DATE", + derivative: "SIMPLE_TEXT", + json: "JSON", + percentage: "PERCENTAGE", + status: "STATUS", + code: "CODE", + multiSelect: "MULTI_SELECT", + rating: "RATING", + toggle: "CHECK_BOX", + file: "FILE", + id: "ID", + duration: "DURATION", + _updatedBy: "USER", + table2Only: "SIMPLE_TEXT", + subTableTest: "SUB_TABLE", +}; +export const triggerPath = "demoAllFieldTypes/{docId}"; +export const functionName = "demoAllFieldTypes"; +export const derivativesConfig = [ + { + fieldName: "derivative", + evaluate: async ({ row, ref, db, auth, storage, utilFns }) => { + if (row.toggle) return "toggle is on"; + if (row.toggle === false) return "toggle is off"; + else return "toggle is not set"; + }, + listenerFields: ["toggle"], }, - listenerFields: ["toggle"] -}, ] -export const defaultValueConfig = [{ - fieldName: 'toggle', +]; +export const defaultValueConfig = [ + { + fieldName: "toggle", type: "dynamic", - script: async ({ - row, - ref, - db, - auth, - utilFns - }) => { - return Math.random() < 0.5 + script: async ({ row, ref, db, auth, utilFns }) => { + return Math.random() < 0.5; }, }, { - fieldName: 'table2Only', + fieldName: "table2Only", type: "dynamic", - script: async ({ - row, - ref, - db, - auth, - utilFns - }) => { + script: async ({ row, ref, db, auth, utilFns }) => { return "test"; }, }, -] -export const documentSelectConfig = [{ - fieldName: 'connectTable', - trackedFields: ["signedUp", - "firstName", - "email" - ] -}, ] -export const extensionsConfig = [{ - name: "email", - type: "sendgridEmail", - triggers: ["update"], - conditions: async ({ - row, - change - }) => { - // feel free to add your own code logic here - return true; +]; +export const documentSelectConfig = [ + { + fieldName: "connectTable", + trackedFields: ["signedUp", "firstName", "email"], }, - requiredFields: ["email", "shortText"], - extensionBody: async ({ - row, - db, - change, - ref - }) => { - // feel free to add your own code logic here +]; +export const extensionsConfig = [ + { + name: "email", + type: "sendgridEmail", + triggers: ["update"], + conditions: async ({ row, change }) => { + // feel free to add your own code logic here + return true; + }, + requiredFields: ["email", "shortText"], + extensionBody: async ({ row, db, change, ref }) => { + // feel free to add your own code logic here - return ({ - from: "Name", // send from field - personalizations: [{ - to: [{ - name: "", - email: "" - }], // recipient - dynamic_template_data: {}, // template parameters - }, ], - template_id: "", // sendgrid template ID - categories: [], // helper info to categorise sendgrid emails - }) - } -}] \ No newline at end of file + return { + from: "Name", // send from field + personalizations: [ + { + to: [ + { + name: "", + email: "", + }, + ], // recipient + dynamic_template_data: {}, // template parameters + }, + ], + template_id: "", // sendgrid template ID + categories: [], // helper info to categorise sendgrid emails + }; + }, + }, +]; diff --git a/src/functionBuilder/functions/src/index.ts b/src/functionBuilder/functions/src/index.ts index 6bb38b9..34ba1fc 100644 --- a/src/functionBuilder/functions/src/index.ts +++ b/src/functionBuilder/functions/src/index.ts @@ -68,5 +68,5 @@ export const R = { } catch (err) { console.log(`caught error: ${err}`); } - }) + }), }; diff --git a/src/functionBuilder/functions/src/initialize/index.ts b/src/functionBuilder/functions/src/initialize/index.ts index 329d2be..0bb5f59 100644 --- a/src/functionBuilder/functions/src/initialize/index.ts +++ b/src/functionBuilder/functions/src/initialize/index.ts @@ -1,30 +1,31 @@ import * as functions from "firebase-functions"; import utilFns from "../utils"; import { db, auth, storage } from "../firebaseConfig"; -const initializedDoc = ( - columns: { fieldName: string; type: string; value?: any; script?: any }[] -) => async (snapshot: functions.firestore.DocumentSnapshot) => - columns.reduce(async (acc, column) => { - if (snapshot.get(column.fieldName) !== undefined) return { ...(await acc) }; // prevents overwriting already initialised values - if (column.type === "static") { - return { - ...(await acc), - [column.fieldName]: column.value, - }; - } else if (column.type === "null") { - return { ...(await acc), [column.fieldName]: null }; - } else if (column.type === "dynamic") { - return { - ...(await acc), - [column.fieldName]: await column.script({ - row: snapshot.data(), - ref: snapshot.ref, - db, - auth, - storage, - utilFns, - }), - }; - } else return { ...(await acc) }; - }, {}); +const initializedDoc = + (columns: { fieldName: string; type: string; value?: any; script?: any }[]) => + async (snapshot: functions.firestore.DocumentSnapshot) => + columns.reduce(async (acc, column) => { + if (snapshot.get(column.fieldName) !== undefined) + return { ...(await acc) }; // prevents overwriting already initialised values + if (column.type === "static") { + return { + ...(await acc), + [column.fieldName]: column.value, + }; + } else if (column.type === "null") { + return { ...(await acc), [column.fieldName]: null }; + } else if (column.type === "dynamic") { + return { + ...(await acc), + [column.fieldName]: await column.script({ + row: snapshot.data(), + ref: snapshot.ref, + db, + auth, + storage, + utilFns, + }), + }; + } else return { ...(await acc) }; + }, {}); export default initializedDoc; diff --git a/src/functionBuilder/functions/src/utils/index.ts b/src/functionBuilder/functions/src/utils/index.ts index b05e35d..dc3cd13 100644 --- a/src/functionBuilder/functions/src/utils/index.ts +++ b/src/functionBuilder/functions/src/utils/index.ts @@ -57,14 +57,20 @@ export const rowReducer = (fieldsToSync, row) => else return acc; }, {}); - -const hasChanged =(change:functions.Change)=> (trackedFields:string[]) =>{ +const hasChanged = + (change: functions.Change) => + (trackedFields: string[]) => { const before = change.before?.data(); const after = change.after?.data(); - if (!before && after)return true; - else if (before && !after)return false; - else return trackedFields.some(trackedField =>JSON.stringify(before[trackedField]) !== JSON.stringify(after[trackedField])) - } + if (!before && after) return true; + else if (before && !after) return false; + else + return trackedFields.some( + (trackedField) => + JSON.stringify(before[trackedField]) !== + JSON.stringify(after[trackedField]) + ); + }; export default { hasChanged, getSecret, diff --git a/src/functionBuilder/index.ts b/src/functionBuilder/index.ts index 3064407..29d2c2f 100644 --- a/src/functionBuilder/index.ts +++ b/src/functionBuilder/index.ts @@ -1,15 +1,17 @@ import { asyncExecute } from "./compiler/terminal"; import { createStreamLogger } from "./utils"; import generateConfig from "./compiler"; -import {functionNamer} from './compiler/loader' +import { functionNamer } from "./compiler/loader"; import { commandErrorHandler } from "./utils"; import firebase from "firebase-admin"; -export const functionBuilder = async (req: any, res: any) => { - const user: firebase.auth.UserRecord = res.locals.user;; - const {triggerPath} = req.body; +export const functionBuilder = async (req: any, res: any) => { + const user: firebase.auth.UserRecord = res.locals.user; + const { triggerPath } = req.body; if (!triggerPath) res.send({ success: false, message: "no triggerPath" }); - const functionConfigPath = `_rowy_/settings/functions/${functionNamer(triggerPath)}` + const functionConfigPath = `_rowy_/settings/functions/${functionNamer( + triggerPath + )}`; const streamLogger = await createStreamLogger(functionConfigPath); await streamLogger.info("streamLogger created"); @@ -24,10 +26,10 @@ export const functionBuilder = async (req: any, res: any) => { return; } await streamLogger.info("generateConfig success"); - - const projectId =process.env.DEV? - require("../../firebase-adminsdk.json").project_id - :require("../../rowyConfig.json").projectId + + const projectId = process.env.DEV + ? require("../../firebase-adminsdk.json").project_id + : require("../../rowyConfig.json").projectId; console.log(`deploying to ${projectId}`); await asyncExecute( `cd build/functionBuilder/functions; \ @@ -47,4 +49,4 @@ export const functionBuilder = async (req: any, res: any) => { res.send({ success: true, }); -} \ No newline at end of file +}; diff --git a/src/functionBuilder/utils.ts b/src/functionBuilder/utils.ts index 7fd53b0..e3b7236 100644 --- a/src/functionBuilder/utils.ts +++ b/src/functionBuilder/utils.ts @@ -12,7 +12,7 @@ function rowyUser(user: admin.auth.UserRecord) { }; } -async function insertErrorToStreamer(errorRecord: any, streamLogger:any) { +async function insertErrorToStreamer(errorRecord: any, streamLogger: any) { let errorString = ""; for (const key of [ "command", @@ -37,9 +37,9 @@ function commandErrorHandler( functionConfigTs?: string; sparksConfig?: string; }, - streamLogger:any + streamLogger: any ) { - return async function (error:any, stdout:any, stderr:any) { + return async function (error: any, stdout: any, stderr: any) { await streamLogger.info(stdout); if (!error) { @@ -70,7 +70,7 @@ async function logErrorToDB( user: admin.auth.UserRecord; sparksConfig?: string; }, - streamLogger:any + streamLogger: any ) { console.error(data.errorDescription); @@ -91,7 +91,7 @@ async function logErrorToDB( function parseSparksConfig( sparks: string | undefined, user: admin.auth.UserRecord, - streamLogger:any + streamLogger: any ) { if (sparks) { try { @@ -99,7 +99,7 @@ function parseSparksConfig( return sparks .replace(/^(\s*)sparks.config\(/, "") .replace(/\);?\s*$/, ""); - } catch (error:any) { + } catch (error: any) { logErrorToDB( { errorDescription: "Sparks is not wrapped with sparks.config", @@ -159,7 +159,7 @@ async function createStreamLogger(tableConfigPath: string) { const logsDoc = await logRef.get(); const errorLog = logsDoc .get("fullLog") - .filter((log:any) => log.level === "error"); + .filter((log: any) => log.level === "error"); if (errorLog.length !== 0) { console.log("streamLogger marked as FAIL"); await logRef.update({ diff --git a/src/hooks/createRowyApp.ts b/src/hooks/createRowyApp.ts index ef00a4d..3c0e17b 100644 --- a/src/hooks/createRowyApp.ts +++ b/src/hooks/createRowyApp.ts @@ -1,60 +1,60 @@ -const client = require("firebase-tools") -import { httpsPost } from '../utils' -import {updateConfig} from './utils' -const getRowyApp = (projectId:string) => +const client = require("firebase-tools"); +import { httpsPost } from "../utils"; +import { updateConfig } from "./utils"; +const getRowyApp = (projectId: string) => new Promise((resolve) => { - const getSDKConfig = (appId:string) => + const getSDKConfig = (appId: string) => client.apps.sdkconfig("web", appId, { project: projectId }); client.apps .list("WEB", { project: projectId }) - .then((data:{displayName:string,appId:string}[]) => { + .then((data: { displayName: string; appId: string }[]) => { const filteredConfigs = data.filter( (config) => config.displayName === "rowyApp" ); if (filteredConfigs.length === 0) { client.apps .create("WEB", "rowyApp", { project: projectId }) - .then((newApp:{appId:string}) => { - getSDKConfig(newApp.appId).then((config:any) => { + .then((newApp: { appId: string }) => { + getSDKConfig(newApp.appId).then((config: any) => { resolve(config.sdkConfig); }); }) - .catch((err:any) => { + .catch((err: any) => { console.error(err); }); } else { - getSDKConfig(filteredConfigs[0].appId).then((config:any) => { + getSDKConfig(filteredConfigs[0].appId).then((config: any) => { resolve(config.sdkConfig); }); } }) - .catch((err:any) => { + .catch((err: any) => { console.error(err); }); }); +const registerRowyApp = async (firebaseConfig: any) => + httpsPost({ + hostname: "us-central1-rowy-service.cloudfunctions.net", + path: `/addProject`, + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: { + firebaseConfig, + }, + }); -const registerRowyApp = async (firebaseConfig:any)=> httpsPost({ - hostname:'us-central1-rowy-service.cloudfunctions.net', - path: `/addProject`, - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body:{ - firebaseConfig - } - }) - -export const createRowyApp = async (projectId:string)=>{ - - try { - const firebaseConfig = await getRowyApp(projectId) - const {secret,success,message}:any = await registerRowyApp(firebaseConfig) - if(!success) throw new Error(message); - updateConfig('secret',secret) - - } catch (error) { - console.log(error) - } -} +export const createRowyApp = async (projectId: string) => { + try { + const firebaseConfig = await getRowyApp(projectId); + const { secret, success, message }: any = await registerRowyApp( + firebaseConfig + ); + if (!success) throw new Error(message); + updateConfig("secret", secret); + } catch (error) { + console.log(error); + } +}; diff --git a/src/hooks/postcreate.ts b/src/hooks/postcreate.ts index 7133eaf..44d6e65 100644 --- a/src/hooks/postcreate.ts +++ b/src/hooks/postcreate.ts @@ -1,17 +1,16 @@ -import {db} from '../firebaseConfig' +import { db } from "../firebaseConfig"; async function start() { - console.log('postcreate') - try { - const update = { - rowyRunBuildStatus: "COMPLETE", - rowyRunUrl: process.env.SERVICE_URL - } - await db.doc("/_rowy_/settings").update(update) - } catch (error) { - console.log(error) - } - + console.log("postcreate"); + try { + const update = { + rowyRunBuildStatus: "COMPLETE", + rowyRunUrl: process.env.SERVICE_URL, + }; + await db.doc("/_rowy_/settings").update(update); + } catch (error) { + console.log(error); + } } -start(); \ No newline at end of file +start(); diff --git a/src/hooks/prebuild.ts b/src/hooks/prebuild.ts index b361731..7d29576 100644 --- a/src/hooks/prebuild.ts +++ b/src/hooks/prebuild.ts @@ -1,41 +1,37 @@ - -import { createRowyApp } from "./createRowyApp" -import { getGCPEmail,updateConfig } from "./utils" -import chalk from 'chalk' -import {db} from '../firebaseConfig' -const projectId = process.env.GOOGLE_CLOUD_PROJECT - +import { createRowyApp } from "./createRowyApp"; +import { getGCPEmail, updateConfig } from "./utils"; +import chalk from "chalk"; +import { db } from "../firebaseConfig"; +const projectId = process.env.GOOGLE_CLOUD_PROJECT; async function start() { - if (!projectId) { - throw new Error("GOOGLE_CLOUD_PROJECT env variable is not set") - } - updateConfig('projectId',projectId) - const settings = { - rowyRunBuildStatus: "BUILDING", - rowyRunRegion: process.env.GOOGLE_CLOUD_REGION, - } - await db.doc("_rowy_/settings").set(settings, { merge: true }) - await createRowyApp(projectId) - const gcpEmail = await getGCPEmail() - if (typeof gcpEmail !== "string") { - throw new Error("cloud shell ") - } - const userManagement = { - owner: { - email: gcpEmail - } - } - await db.doc("_rowy_/userManagement").set(userManagement, { merge: true }) - const publicSettings = { - signInOptions: [ - "google" - ] - } - await db.doc("_rowy_/publicSettings").set(publicSettings, { merge: true }) - console.log(chalk.green("Successfully created rowy app")) - const rowyAppURL = `https://${projectId}.rowy.app/setup` - console.log(chalk.hex('#4200ff').bold(`Open ${rowyAppURL} to get started`)) + if (!projectId) { + throw new Error("GOOGLE_CLOUD_PROJECT env variable is not set"); + } + updateConfig("projectId", projectId); + const settings = { + rowyRunBuildStatus: "BUILDING", + rowyRunRegion: process.env.GOOGLE_CLOUD_REGION, + }; + await db.doc("_rowy_/settings").set(settings, { merge: true }); + await createRowyApp(projectId); + const gcpEmail = await getGCPEmail(); + if (typeof gcpEmail !== "string") { + throw new Error("cloud shell "); + } + const userManagement = { + owner: { + email: gcpEmail, + }, + }; + await db.doc("_rowy_/userManagement").set(userManagement, { merge: true }); + const publicSettings = { + signInOptions: ["google"], + }; + await db.doc("_rowy_/publicSettings").set(publicSettings, { merge: true }); + console.log(chalk.green("Successfully created rowy app")); + const rowyAppURL = `https://${projectId}.rowy.app/setup`; + console.log(chalk.hex("#4200ff").bold(`Open ${rowyAppURL} to get started`)); } -start(); \ No newline at end of file +start(); diff --git a/src/hooks/utils.ts b/src/hooks/utils.ts index 9bbf610..a04a502 100644 --- a/src/hooks/utils.ts +++ b/src/hooks/utils.ts @@ -1,17 +1,20 @@ -import child from 'child_process' +import child from "child_process"; -export const getGCPEmail = () => new Promise(async (resolve, reject) => { +export const getGCPEmail = () => + new Promise(async (resolve, reject) => { child.exec("gcloud auth list", async function (error, stdout, stderr) { - if(error)reject(error) - const match = stdout.match(/(?=\*).*/) - if(match)resolve(match[0].replace("*","").trim()) - else reject(new Error("No match")) + if (error) reject(error); + const match = stdout.match(/(?=\*).*/); + if (match) resolve(match[0].replace("*", "").trim()); + else reject(new Error("No match")); }); -}) - + }); export const updateConfig = (key: string, value: any) => { - let rowyService = require('../../rowyConfig.json') - rowyService[key] = value - require('fs').writeFileSync('../../rowyConfig.json',JSON.stringify(rowyService,null,2)) -} \ No newline at end of file + let rowyService = require("../../rowyConfig.json"); + rowyService[key] = value; + require("fs").writeFileSync( + "../../rowyConfig.json", + JSON.stringify(rowyService, null, 2) + ); +}; diff --git a/src/index.ts b/src/index.ts index 00fd4df..79bc105 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,17 +1,21 @@ -import express from 'express' -import { hasAnyRole,requireAuth } from './middleware/auth' -import { deleteUser, impersonateUser, inviteUser, setUserRoles } from './userManagement'; -import { getFirestoreRules, listCollections } from './firestore'; -import { actionScript } from './actionScripts'; -import { functionBuilder } from './functionBuilder'; -import {version,region,serviceAccountAccess, setOwnerRoles} from './setup' -import {checkIfFTMigrationRequired,migrateFT2Rowy} from './setup/ft2rowy' -import cors from'cors' +import express from "express"; +import { hasAnyRole, requireAuth } from "./middleware/auth"; +import { + deleteUser, + impersonateUser, + inviteUser, + setUserRoles, +} from "./userManagement"; +import { getFirestoreRules, listCollections } from "./firestore"; +import { actionScript } from "./actionScripts"; +import { functionBuilder } from "./functionBuilder"; +import { version, region, serviceAccountAccess, setOwnerRoles } from "./setup"; +import { checkIfFTMigrationRequired, migrateFT2Rowy } from "./setup/ft2rowy"; +import cors from "cors"; const app = express(); // json is the default content-type for POST requests app.use(express.json()); -app.use(cors()) - +app.use(cors()); const functionWrapper = (fn) => async (req, res) => { try { @@ -23,54 +27,74 @@ const functionWrapper = (fn) => async (req, res) => { }; // rowy Run Setup // get version -app.get('/version',version); -app.get('/region',functionWrapper(region)); +app.get("/version", version); +app.get("/region", functionWrapper(region)); -app.get('/serviceAccountAccess',serviceAccountAccess) +app.get("/serviceAccountAccess", serviceAccountAccess); -app.get('/setOwnerRoles',requireAuth,setOwnerRoles) +app.get("/setOwnerRoles", requireAuth, setOwnerRoles); -app.get('/listCollections', requireAuth,hasAnyRole(["ADMIN"]),functionWrapper(listCollections)); +app.get( + "/listCollections", + requireAuth, + hasAnyRole(["ADMIN"]), + functionWrapper(listCollections) +); -app.get('/firestoreRules',requireAuth,hasAnyRole(["ADMIN","OWNER"]),functionWrapper(getFirestoreRules)) +app.get( + "/firestoreRules", + requireAuth, + hasAnyRole(["ADMIN", "OWNER"]), + functionWrapper(getFirestoreRules) +); //FT Migration -app.get("/checkFT2Rowy",requireAuth,hasAnyRole(["ADMIN","OWNER"]),checkIfFTMigrationRequired) -app.get("/migrateFT2Rowy",requireAuth,hasAnyRole(["ADMIN","OWNER"]),functionWrapper(migrateFT2Rowy)) +app.get( + "/checkFT2Rowy", + requireAuth, + hasAnyRole(["ADMIN", "OWNER"]), + checkIfFTMigrationRequired +); +app.get( + "/migrateFT2Rowy", + requireAuth, + hasAnyRole(["ADMIN", "OWNER"]), + functionWrapper(migrateFT2Rowy) +); // USER MANAGEMENT // invite users -app.post('/inviteUser',requireAuth,hasAnyRole(["ADMIN"]),inviteUser) +app.post("/inviteUser", requireAuth, hasAnyRole(["ADMIN"]), inviteUser); //set user roles -app.post('/setUserRoles',requireAuth,hasAnyRole(["ADMIN"]),setUserRoles) +app.post("/setUserRoles", requireAuth, hasAnyRole(["ADMIN"]), setUserRoles); // delete user -app.delete('/deleteUser',requireAuth,hasAnyRole(["ADMIN"]),deleteUser) +app.delete("/deleteUser", requireAuth, hasAnyRole(["ADMIN"]), deleteUser); // impersonate user -app.get('/impersonateUser/:email',requireAuth,hasAnyRole(["ADMIN"]),impersonateUser) +app.get( + "/impersonateUser/:email", + requireAuth, + hasAnyRole(["ADMIN"]), + impersonateUser +); // action script -app.post('/actionScript',requireAuth,actionScript) +app.post("/actionScript", requireAuth, actionScript); // Function Builder -app.post('/buildFunction',requireAuth,hasAnyRole(["ADMIN"]),functionBuilder) +app.post("/buildFunction", requireAuth, hasAnyRole(["ADMIN"]), functionBuilder); //SECRET MANAGEMENT // get secret - - - const port = process.env.PORT || 8080; app.listen(port, () => { console.log(`rowyRun: listening on port ${port}`); }); - - // Exports for testing purposes. module.exports = app; diff --git a/src/metadataInstance.ts b/src/metadataInstance.ts index df3e55f..5257f7b 100644 --- a/src/metadataInstance.ts +++ b/src/metadataInstance.ts @@ -1,16 +1,12 @@ - - - -const axios = require('axios'); +const axios = require("axios"); const axiosInstance = axios.create({ - baseURL: 'http://metadata.google.internal/', - timeout: 1000, - headers: {'Metadata-Flavor': 'Google'} - }); - + baseURL: "http://metadata.google.internal/", + timeout: 1000, + headers: { "Metadata-Flavor": "Google" }, +}); // let path = req.query.path || 'computeMetadata/v1/instance/service-accounts/default/scopes'; // axiosInstance.get(path).then(response => { // res.send({data:response.data}); -// }); \ No newline at end of file +// }); diff --git a/src/middleware/auth.ts b/src/middleware/auth.ts index 62f6b31..5d4aaf4 100644 --- a/src/middleware/auth.ts +++ b/src/middleware/auth.ts @@ -1,35 +1,38 @@ -import { auth } from '../firebaseConfig'; +import { auth } from "../firebaseConfig"; - -export const requireAuth = async(req: any, res: any, next: any) => { - try { - const authHeader = req.get('Authorization'); - if (!authHeader) return res.status(401).send('Unauthorized'); - const authToken = authHeader.split(' ')[1]; +export const requireAuth = async (req: any, res: any, next: any) => { + try { + const authHeader = req.get("Authorization"); + if (!authHeader) return res.status(401).send("Unauthorized"); + const authToken = authHeader.split(" ")[1]; const decodedToken = await auth.verifyIdToken(authToken); const uid = decodedToken.uid; const user = await auth.getUser(uid); res.locals.user = user; next(); -} catch (error) { + } catch (error) { console.error(error); - res.status(401).send({error}); -} - -} + res.status(401).send({ error }); + } +}; -export const hasAnyRole = (roles: string[]) => async (req: any, res: any, next: Function) => { - try { - const user = res.locals.user; - const userRoles :string[] = user.customClaims.roles; - // user roles must have at least one of the roles - const authorized = roles.some(role => userRoles.includes(role)); - if (authorized) { - next(); - } else { - res.status(401).send({ error: 'Unauthorized', message: 'User does not have any of the required roles', roles }); +export const hasAnyRole = + (roles: string[]) => async (req: any, res: any, next: Function) => { + try { + const user = res.locals.user; + const userRoles: string[] = user.customClaims.roles; + // user roles must have at least one of the roles + const authorized = roles.some((role) => userRoles.includes(role)); + if (authorized) { + next(); + } else { + res.status(401).send({ + error: "Unauthorized", + message: "User does not have any of the required roles", + roles, + }); + } + } catch (err) { + res.status(401).send({ error: err }); } - } catch (err) { - res.status(401).send({ error:err }); - } -} \ No newline at end of file + }; diff --git a/src/setup/ft2rowy.ts b/src/setup/ft2rowy.ts index 2d7bca8..33a49ac 100644 --- a/src/setup/ft2rowy.ts +++ b/src/setup/ft2rowy.ts @@ -1,60 +1,80 @@ // firetable migration -import { Request,Response } from "express"; -import {db} from '../firebaseConfig' +import { Request, Response } from "express"; +import { db } from "../firebaseConfig"; -const ROWY_SETTINGS = "_rowy_/settings" -const ROWY_TABLE_SCHEMAS = `${ROWY_SETTINGS}/schema` -const ROWY_GROUP_TABLE_SCHEMAS = `${ROWY_SETTINGS}/groupSchema` -const FT_SETTINGS = "_FIRETABLE_/settings" -const FT_TABLE_SCHEMAS = `${FT_SETTINGS}/schema` -const FT_GROUP_TABLE_SCHEMAS = `${FT_SETTINGS}/groupSchema` +const ROWY_SETTINGS = "_rowy_/settings"; +const ROWY_TABLE_SCHEMAS = `${ROWY_SETTINGS}/schema`; +const ROWY_GROUP_TABLE_SCHEMAS = `${ROWY_SETTINGS}/groupSchema`; +const FT_SETTINGS = "_FIRETABLE_/settings"; +const FT_TABLE_SCHEMAS = `${FT_SETTINGS}/schema`; +const FT_GROUP_TABLE_SCHEMAS = `${FT_SETTINGS}/groupSchema`; -export const checkIfFTMigrationRequired = async (req:Request,res:Response) => { +export const checkIfFTMigrationRequired = async ( + req: Request, + res: Response +) => { const ftSettingsExists = (await db.doc(FT_SETTINGS).get()).exists; - if (!ftSettingsExists) return res.send({migrationRequired: false,message:"Firetable Settings doesn't exist"}); + if (!ftSettingsExists) + return res.send({ + migrationRequired: false, + message: "Firetable Settings doesn't exist", + }); const migrated = (await db.doc(ROWY_SETTINGS).get()).data()?.migratedToV2 !== undefined; - if (migrated) return res.send({migrationRequired: false,message:"Firetable Settings has already been migrated"}); - const tableSchemas = ( - await db.collection(FT_TABLE_SCHEMAS).get() - ).size; - - const groupTableSchemas = ( - await db.collection(FT_GROUP_TABLE_SCHEMAS).get() - ).size; - if(tableSchemas + groupTableSchemas === 0)return res.send({migrationRequired: false,message:"Firetable Settings has no tables"}); - return res.send({migrationRequired: true,message:"Firetable Settings needs to be migrated"}); -} - + if (migrated) + return res.send({ + migrationRequired: false, + message: "Firetable Settings has already been migrated", + }); + const tableSchemas = (await db.collection(FT_TABLE_SCHEMAS).get()).size; + const groupTableSchemas = (await db.collection(FT_GROUP_TABLE_SCHEMAS).get()) + .size; + if (tableSchemas + groupTableSchemas === 0) + return res.send({ + migrationRequired: false, + message: "Firetable Settings has no tables", + }); + return res.send({ + migrationRequired: true, + message: "Firetable Settings needs to be migrated", + }); +}; export const migrateFT2Rowy = async () => { - const migrated = (await db.doc(ROWY_SETTINGS).get()).data()?.migratedToV2 !== undefined; + const migrated = + (await db.doc(ROWY_SETTINGS).get()).data()?.migratedToV2 !== undefined; if (migrated) throw new Error("Migration has already been done"); - const ftSettingsDoc = await db.doc(FT_SETTINGS).get() - const oldTables = ftSettingsDoc.get("tables") - const newTables = oldTables.map(oldTable => { - const {table, collection,...rest} = oldTable + const ftSettingsDoc = await db.doc(FT_SETTINGS).get(); + const oldTables = ftSettingsDoc.get("tables"); + const newTables = oldTables.map((oldTable) => { + const { table, collection, ...rest } = oldTable; return { - id:collection, + id: collection, table, collection, - ...rest - } - }) - await db - .doc(ROWY_SETTINGS) - .set({ table: newTables}, { merge: true }); + ...rest, + }; + }); + await db.doc(ROWY_SETTINGS).set({ table: newTables }, { merge: true }); const tables = await db.collection(FT_TABLE_SCHEMAS).get(); const groupTables = await db.collection(FT_GROUP_TABLE_SCHEMAS).get(); - const promises = [...tables.docs.map((table) => - db - .collection(ROWY_TABLE_SCHEMAS) - .doc(table.id) - .set(table.data(), { merge: true }) - ),...groupTables.docs.map((table) =>db.collection(ROWY_GROUP_TABLE_SCHEMAS).doc(table.id).set(table.data(), { merge: true }))] + const promises = [ + ...tables.docs.map((table) => + db + .collection(ROWY_TABLE_SCHEMAS) + .doc(table.id) + .set(table.data(), { merge: true }) + ), + ...groupTables.docs.map((table) => + db + .collection(ROWY_GROUP_TABLE_SCHEMAS) + .doc(table.id) + .set(table.data(), { merge: true }) + ), + ]; await Promise.all(promises); await db.doc(ROWY_SETTINGS).update({ migratedToV2: true }); - return {success:true} + return { success: true }; }; diff --git a/src/setup/index.ts b/src/setup/index.ts index bb70f99..4d18e75 100644 --- a/src/setup/index.ts +++ b/src/setup/index.ts @@ -1,4 +1,4 @@ -export {version} from './version'; -export {region} from './region'; -export {serviceAccountAccess} from './serviceAccountAccess' -export {setOwnerRoles} from './setOwnerRoles' \ No newline at end of file +export { version } from "./version"; +export { region } from "./region"; +export { serviceAccountAccess } from "./serviceAccountAccess"; +export { setOwnerRoles } from "./setOwnerRoles"; diff --git a/src/setup/region.ts b/src/setup/region.ts index 1b17c7f..5a6c00e 100644 --- a/src/setup/region.ts +++ b/src/setup/region.ts @@ -1,5 +1,5 @@ -import {db} from '../firebaseConfig' -export const region = async ()=>{ - const settings = await db.doc('_rowy_/settings').get() - return {region:settings.data().rowyRunRegion} -} +import { db } from "../firebaseConfig"; +export const region = async () => { + const settings = await db.doc("_rowy_/settings").get(); + return { region: settings.data().rowyRunRegion }; +}; diff --git a/src/setup/serviceAccountAccess.ts b/src/setup/serviceAccountAccess.ts index 04f1f83..6d0fe3d 100644 --- a/src/setup/serviceAccountAccess.ts +++ b/src/setup/serviceAccountAccess.ts @@ -1,46 +1,43 @@ -import { Request,Response } from "express"; -import {db,auth,admin} from '../firebaseConfig' - -export const serviceAccountAccess = async(req:Request, res:Response) => { - try { - const access:any = { - - } - // test access to firestore - try { - await db.listCollections() - const testDocRef = db.doc("_rowy_/testingAccess") - await testDocRef.set({success:true}) - const testDoc = await testDocRef.get() - if(!testDoc.exists) access.firestore = false - await testDocRef.delete - access.firestore = true - } catch (error) { - access.firestore = false - } - // test access to auth - try { - const testUser = await auth.createUser({ - email:"test@test.rowy" - }) - await auth.deleteUser(testUser.uid) - access.auth = true - } - catch (error) { - access.auth = false - } +import { Request, Response } from "express"; +import { db, auth, admin } from "../firebaseConfig"; - // test access to firestore rules - try { - const securityRules = admin.securityRules() - await securityRules.getFirestoreRuleset() - access.firestoreRules = true - } catch (error) { - access.firestoreRules = false - } +export const serviceAccountAccess = async (req: Request, res: Response) => { + try { + const access: any = {}; + // test access to firestore + try { + await db.listCollections(); + const testDocRef = db.doc("_rowy_/testingAccess"); + await testDocRef.set({ success: true }); + const testDoc = await testDocRef.get(); + if (!testDoc.exists) access.firestore = false; + await testDocRef.delete; + access.firestore = true; + } catch (error) { + access.firestore = false; + } + // test access to auth + try { + const testUser = await auth.createUser({ + email: "test@test.rowy", + }); + await auth.deleteUser(testUser.uid); + access.auth = true; + } catch (error) { + access.auth = false; + } - res.send(access) + // test access to firestore rules + try { + const securityRules = admin.securityRules(); + await securityRules.getFirestoreRuleset(); + access.firestoreRules = true; } catch (error) { - res.send({error}) + access.firestoreRules = false; } -} \ No newline at end of file + + res.send(access); + } catch (error) { + res.send({ error }); + } +}; diff --git a/src/setup/setOwnerRoles.ts b/src/setup/setOwnerRoles.ts index 743b717..5ab9886 100644 --- a/src/setup/setOwnerRoles.ts +++ b/src/setup/setOwnerRoles.ts @@ -1,21 +1,29 @@ - -import { Request,Response } from "express"; -import {auth,db} from '../firebaseConfig' -export const setOwnerRoles = async (req:Request, res:Response) => { - try { - const userManagementDoc = await db.doc('_rowy_/userManagement').get() - const user = res.locals.user - const ownerEmail = userManagementDoc.get('owner.email') - if (user.email !== ownerEmail) return res.send({success:false,message:"Logged in user is not the owner",ownerEmail,userEmail:user.email}); - await auth.setCustomUserClaims(user.uid, { - ...user.customClaims, - roles:[ - "ADMIN", - "OWNER" - ]}) - const updatedUser = await auth.getUser(user.uid) - return res.send({ success: true,ownerEmail,user,newClaims:updatedUser.customClaims}); - } catch (error) { - return res.send({ success: false, error: error }); - } -} \ No newline at end of file +import { Request, Response } from "express"; +import { auth, db } from "../firebaseConfig"; +export const setOwnerRoles = async (req: Request, res: Response) => { + try { + const userManagementDoc = await db.doc("_rowy_/userManagement").get(); + const user = res.locals.user; + const ownerEmail = userManagementDoc.get("owner.email"); + if (user.email !== ownerEmail) + return res.send({ + success: false, + message: "Logged in user is not the owner", + ownerEmail, + userEmail: user.email, + }); + await auth.setCustomUserClaims(user.uid, { + ...user.customClaims, + roles: ["ADMIN", "OWNER"], + }); + const updatedUser = await auth.getUser(user.uid); + return res.send({ + success: true, + ownerEmail, + user, + newClaims: updatedUser.customClaims, + }); + } catch (error) { + return res.send({ success: false, error: error }); + } +}; diff --git a/src/setup/version.ts b/src/setup/version.ts index 38bbffa..4ba174d 100644 --- a/src/setup/version.ts +++ b/src/setup/version.ts @@ -1,7 +1,5 @@ - -import { Request,Response } from "express"; -const meta = require('../../package.json') -export const version = async (req:Request, res:Response) => { - - res.send({ version: meta.version,}); - } \ No newline at end of file +import { Request, Response } from "express"; +const meta = require("../../package.json"); +export const version = async (req: Request, res: Response) => { + res.send({ version: meta.version }); +}; diff --git a/src/userManagement/deleteUser.ts b/src/userManagement/deleteUser.ts index 2600337..16a5136 100644 --- a/src/userManagement/deleteUser.ts +++ b/src/userManagement/deleteUser.ts @@ -1,24 +1,26 @@ -import { auth, db } from '../firebaseConfig' -import { Request, Response } from 'express' -import { rowyUsers } from '../constants/Collections'; +import { auth, db } from "../firebaseConfig"; +import { Request, Response } from "express"; +import { rowyUsers } from "../constants/Collections"; export const deleteUser = async (req: Request, res: Response) => { try { const { email } = req.body; // check if user exists - const userQuery = await db.collection(rowyUsers).where('email', '==', email).get() + const userQuery = await db + .collection(rowyUsers) + .where("email", "==", email) + .get(); if (userQuery.docs.length === 0) { - throw new Error('User does not exist') + throw new Error("User does not exist"); } - const userDoc = userQuery.docs[0] - await userDoc.ref.delete() + const userDoc = userQuery.docs[0]; + await userDoc.ref.delete(); try { - await auth.deleteUser(userDoc.id) + await auth.deleteUser(userDoc.id); } catch (error) { - console.log(error) + console.log(error); } res.send({ success: true }); - } catch (error: any) { res.send({ error: error.message }); } -} \ No newline at end of file +}; diff --git a/src/userManagement/impersonateUser.ts b/src/userManagement/impersonateUser.ts index c7b066c..b540178 100644 --- a/src/userManagement/impersonateUser.ts +++ b/src/userManagement/impersonateUser.ts @@ -1,22 +1,26 @@ -import { auth, db } from '../firebaseConfig' -import { Request, Response } from 'express' -import { rowyUsersImpersonationLogs } from '../constants/Collections'; +import { auth, db } from "../firebaseConfig"; +import { Request, Response } from "express"; +import { rowyUsersImpersonationLogs } from "../constants/Collections"; export const impersonateUser = async (req: Request, res: Response) => { try { - const impersonator = res.locals.user + const impersonator = res.locals.user; const { email } = req.params; // check if user exists const user = await auth.getUserByEmail(email); const token = await auth.createCustomToken(user.uid); await db.collection(rowyUsersImpersonationLogs).add({ - createdAt: new Date(), - impersonatedUid: user.uid, - impersonatedUserEmail: email, - impersonatorUid:impersonator.uid, - impersonatorEmail:impersonator.email, - }) - res.send({ success: true ,token,message:`Authenticating as ${user.displayName}`}); + createdAt: new Date(), + impersonatedUid: user.uid, + impersonatedUserEmail: email, + impersonatorUid: impersonator.uid, + impersonatorEmail: impersonator.email, + }); + res.send({ + success: true, + token, + message: `Authenticating as ${user.displayName}`, + }); } catch (error) { - res.send({ error,success: false }); + res.send({ error, success: false }); } -} \ No newline at end of file +}; diff --git a/src/userManagement/index.ts b/src/userManagement/index.ts index a8ac03b..801d207 100644 --- a/src/userManagement/index.ts +++ b/src/userManagement/index.ts @@ -1,4 +1,4 @@ -export {inviteUser} from './inviteUser'; -export {deleteUser} from './deleteUser'; -export {setUserRoles} from './setUserRoles'; -export {impersonateUser} from './impersonateUser'; +export { inviteUser } from "./inviteUser"; +export { deleteUser } from "./deleteUser"; +export { setUserRoles } from "./setUserRoles"; +export { impersonateUser } from "./impersonateUser"; diff --git a/src/userManagement/inviteUser.ts b/src/userManagement/inviteUser.ts index ca4fc98..72b4f58 100644 --- a/src/userManagement/inviteUser.ts +++ b/src/userManagement/inviteUser.ts @@ -1,28 +1,30 @@ -import { auth, db } from '../firebaseConfig' -import { Request, Response } from 'express' -import { rowyUsers } from '../constants/Collections'; +import { auth, db } from "../firebaseConfig"; +import { Request, Response } from "express"; +import { rowyUsers } from "../constants/Collections"; export const inviteUser = async (req: Request, res: Response) => { - try { - const { email, roles } = req.body; - // check if user exists - const userQuery = await db.collection(rowyUsers).where('email', '==', email).get() - if (userQuery.docs.length !== 0) { - throw new Error('User already exists'); - } - const user = await auth.getUserByEmail(email); - if (!user) { - // create user - const newUser = await auth.createUser({ - email, - displayName: email, - disabled: false - }); - // roles - auth.setCustomUserClaims(newUser.uid, { roles }); - } - res.send({success: true}); - - } catch (error: any) { - res.send({ error: error.message }); + try { + const { email, roles } = req.body; + // check if user exists + const userQuery = await db + .collection(rowyUsers) + .where("email", "==", email) + .get(); + if (userQuery.docs.length !== 0) { + throw new Error("User already exists"); } -} \ No newline at end of file + const user = await auth.getUserByEmail(email); + if (!user) { + // create user + const newUser = await auth.createUser({ + email, + displayName: email, + disabled: false, + }); + // roles + auth.setCustomUserClaims(newUser.uid, { roles }); + } + res.send({ success: true }); + } catch (error: any) { + res.send({ error: error.message }); + } +}; diff --git a/src/userManagement/setUserRoles.ts b/src/userManagement/setUserRoles.ts index 924c76f..7fde460 100644 --- a/src/userManagement/setUserRoles.ts +++ b/src/userManagement/setUserRoles.ts @@ -1,21 +1,24 @@ -import { auth, db } from '../firebaseConfig' -import { Request, Response } from 'express' -import { rowyUsers } from '../constants/Collections'; +import { auth, db } from "../firebaseConfig"; +import { Request, Response } from "express"; +import { rowyUsers } from "../constants/Collections"; export const setUserRoles = async (req: Request, res: Response) => { - try { - const { email, roles } = req.body; - // check if user exists - const userQuery = await db.collection(rowyUsers).where('email', '==', email).get() - if (userQuery.docs.length === 0) { - throw new Error('User does not exist') - } - const uid = userQuery.docs[0].id - Promise.all([ - userQuery.docs[0].ref.update({roles}), - auth.setCustomUserClaims(uid, { roles }) - ]) - res.send({success: true}); - } catch (error: any) { - res.send({ error: error.message }); + try { + const { email, roles } = req.body; + // check if user exists + const userQuery = await db + .collection(rowyUsers) + .where("email", "==", email) + .get(); + if (userQuery.docs.length === 0) { + throw new Error("User does not exist"); } -} \ No newline at end of file + const uid = userQuery.docs[0].id; + Promise.all([ + userQuery.docs[0].ref.update({ roles }), + auth.setCustomUserClaims(uid, { roles }), + ]); + res.send({ success: true }); + } catch (error: any) { + res.send({ error: error.message }); + } +}; diff --git a/src/utils.ts b/src/utils.ts index 7f0b154..a2471d7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,23 +1,26 @@ -import https from 'https' -export function httpsPost({body, ...options}:any){ - return new Promise((resolve,reject) => { - const req = https.request({ - method: 'POST', - ...options, - }, res => { - res.setEncoding('utf8') - let body = '' - res.on('data', chunk => { - body += chunk - }) - res.on('end', () => { - resolve(JSON.parse(body)) - }) - }) - req.on('error',reject); - if(body) { - req.write(JSON.stringify(body)); +import https from "https"; +export function httpsPost({ body, ...options }: any) { + return new Promise((resolve, reject) => { + const req = https.request( + { + method: "POST", + ...options, + }, + (res) => { + res.setEncoding("utf8"); + let body = ""; + res.on("data", (chunk) => { + body += chunk; + }); + res.on("end", () => { + resolve(JSON.parse(body)); + }); } - req.end(); - }) + ); + req.on("error", reject); + if (body) { + req.write(JSON.stringify(body)); + } + req.end(); + }); }