Merge pull request #129 from AntlerVC/develop

Develop
This commit is contained in:
Shams
2020-06-19 14:59:42 +08:00
committed by GitHub
12 changed files with 137 additions and 436 deletions

13
Dockerfile Normal file
View File

@@ -0,0 +1,13 @@
# Stage 1
FROM node:8 as react-build
WORKDIR /www
COPY . ./
RUN yarn
RUN yarn build
# Stage 2 - the production environment
FROM nginx:alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=react-build /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

38
app.json Normal file
View File

@@ -0,0 +1,38 @@
{
"name": "foo-app",
"env": {
"BACKGROUND_COLOR": {
"description": "specify a css color",
"value": "#fefefe",
"required": false
},
"TITLE": {
"description": "title for your site"
},
"APP_SECRET": {
"generator": "secret"
}
},
"options": {
"allow-unauthenticated": false,
"memory": "512Mi",
"cpu": "1"
},
"build": {
"skip": false
},
"hooks": {
"prebuild": {
"commands": ["./my-custom-prebuild"]
},
"postbuild": {
"commands": ["./my-custom-postbuild"]
},
"precreate": {
"commands": ["echo 'test'"]
},
"postcreate": {
"commands": ["./setup.sh"]
}
}
}

View File

@@ -1,7 +1,6 @@
{
"functions": {
"predeploy": [
"npm --prefix \"$RESOURCE_DIR\" run fetchConfig",
"npm --prefix \"$RESOURCE_DIR\" run lint",
"npm --prefix \"$RESOURCE_DIR\" run build"
]

View File

@@ -3,44 +3,57 @@ import * as fs from "fs";
import * as admin from "firebase-admin";
// Initialize Firebase Admin
const serviceAccount = require(`./firebase-credentials.json`);
console.log(`Running on ${serviceAccount.project_id}`);
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: `https://${serviceAccount.project_id}.firebaseio.com`,
});
const db = admin.firestore();
const serviceAccount = requireIfExists(`./firebase-credentials.json`);
const docConfig2json = async (docPath: string, jsonPath: string) => {
const doc = await db.doc(docPath).get();
const data = doc.data();
const jsonData = JSON.stringify(data ? data.config : "");
fs.writeFileSync(jsonPath, jsonData);
};
function requireIfExists(module) {
try {
return require(module);
} catch (error) {
// pass and try next file
console.log("serviceAccount json not found");
return false;
}
// throw('None of the provided modules exist.')
}
if (serviceAccount) {
console.log(`Running on ${serviceAccount.project_id}`);
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: `https://${serviceAccount.project_id}.firebaseio.com`,
});
const db = admin.firestore();
// Initialize Cloud Firestore Database
const docConfig2json = async (docPath: string, jsonPath: string) => {
const doc = await db.doc(docPath).get();
const data = doc.data();
const jsonData = JSON.stringify(data ? data.config : "[]");
fs.writeFileSync(jsonPath, jsonData);
};
const main = async () => {
await docConfig2json(
"_FIRETABLE_/_SETTINGS_/_CONFIG_/_HISTORY_",
"./src/history/config.json"
);
await docConfig2json(
"_FIRETABLE_/_SETTINGS_/_CONFIG_/_ALGOLIA_",
"./src/algolia/config.json"
);
await docConfig2json(
"_FIRETABLE_/_SETTINGS_/_CONFIG_/_COLLECTION_SYNC_",
"./src/collectionSync/config.json"
);
await docConfig2json(
"_FIRETABLE_/_SETTINGS_/_CONFIG_/_SNAPSHOT_SYNC_",
"./src/snapshotSync/config.json"
);
return true;
};
// Initialize Cloud Firestore Database
main()
.catch(err => console.log(err))
.then(() => console.log("this will succeed"))
.catch(() => "obligatory catch");
const main = async () => {
await docConfig2json(
"_FIRETABLE_/_SETTINGS_/_CONFIG_/_HISTORY_",
"./src/history/config.json"
);
await docConfig2json(
"_FIRETABLE_/_SETTINGS_/_CONFIG_/_ALGOLIA_",
"./src/algolia/config.json"
);
await docConfig2json(
"_FIRETABLE_/_SETTINGS_/_CONFIG_/_COLLECTION_SYNC_",
"./src/collectionSync/config.json"
);
await docConfig2json(
"_FIRETABLE_/_SETTINGS_/_CONFIG_/_SNAPSHOT_SYNC_",
"./src/snapshotSync/config.json"
);
return true;
};
main()
.catch(err => console.log(err))
.then(() => console.log("this will succeed"))
.catch(() => "obligatory catch");
}

View File

@@ -7,7 +7,8 @@
"serve": "npm run build && firebase serve --only functions",
"shell": "npm run build && firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy",
"deploy": "firebase deploy --only functions:exportTable",
"deployFT": "firebase deploy",
"logs": "firebase functions:log"
},
"engines": {

View File

@@ -24,7 +24,6 @@ const cloneDoc = (targetCollection: string, fieldsToSync: string[]) => (
) => {
const docId = snapshot.id;
const docData = snapshot.data();
console.log(docData);
if (!docData) return false; // returns if theres no data in the doc
const syncData = fieldsToSync.reduce(docReducer(docData), {});
if (Object.keys(syncData).length === 0) return false; // returns if theres nothing to sync
@@ -33,6 +32,10 @@ const cloneDoc = (targetCollection: string, fieldsToSync: string[]) => (
/\{\{(.*?)\}\}/g,
replacer(docData)
);
if (collectionPath.includes("//")) {
console.log(`${collectionPath} is an invalid collection path`);
return false;
}
db.collection(collectionPath)
.doc(docId)
.set({ ...syncData, syncedAt: new Date() }, { merge: true })
@@ -57,18 +60,17 @@ const syncDoc = (targetCollection: string, fieldsToSync: string[]) => async (
/\{\{(.*?)\}\}/g,
replacer(docData)
);
if (collectionPath.includes("//")) {
console.log(`${collectionPath} is an invalid collection path`);
return false;
}
if (Object.keys(syncData).length === 0) return false; // returns if theres nothing to sync
const targetDoc = await db
.collection(collectionPath)
.doc(docId)
.get();
if (!targetDoc.exists) return false;
const targetDocData = targetDoc.data();
console.log(`syncing ${docId}`, targetDoc.exists, {
targetDocData,
docData,
syncData,
});
db.collection(collectionPath)
.doc(docId)
.update({ ...syncData, syncedAt: new Date() })

View File

@@ -1,3 +1,5 @@
export { exportTable } from "./export";
import algoliaFnsGenerator from "./algolia";
import * as algoliaConfig from "./algolia/config.json";
import collectionSyncFnsGenerator from "./collectionSync";
@@ -12,31 +14,30 @@ import * as collectionHistoryConfig from "./history/config.json";
import permissionControlFnsGenerator from "./permissions";
import * as permissionsConfig from "./permissions/config.json";
import synonymsFnsGenerator from "./synonyms";
import synonymsConfig from "./synonyms/config";
export { exportTable } from "./export";
export { triggerCloudBuild } from "./buildTriggers";
export { scheduledFirestoreBackup, callableFirestoreBackup } from "./backup";
import * as callableFns from "./callable";
export const callable = callableFns;
export const FT_algolia = algoliaConfig.reduce((acc: any, collection) => {
export const FT_algolia = algoliaConfig.reduce((acc: any, collection: any) => {
return { ...acc, [collection.name]: algoliaFnsGenerator(collection) };
}, {});
export const FT_sync = collectionSyncConfig.reduce((acc: any, collection) => {
return {
...acc,
[`${`${`${collection.source}`
.replace(/\//g, "_")
.replace(/_{.*?}_/g, "_")}`}2${`${`${collection.target}`
.replace(/\//g, "_")
.replace(/_{.*?}_/g, "_")}`}`]: collectionSyncFnsGenerator(collection),
};
}, {});
export const FT_sync = collectionSyncConfig.reduce(
(acc: any, collection: any) => {
return {
...acc,
[`${`${`${collection.source}`
.replace(/\//g, "_")
.replace(/_{.*?}_/g, "_")}`}2${`${`${collection.target}`
.replace(/\//g, "_")
.replace(/_{.*?}_/g, "_")}`}`]: collectionSyncFnsGenerator(collection),
};
},
{}
);
export const FT_snapshotSync = snapshotSyncConfig.reduce(
(acc: any, collection) => {
(acc: any, collection: any) => {
if (collection.fnName) {
return {
...acc,
@@ -56,7 +57,7 @@ export const FT_snapshotSync = snapshotSyncConfig.reduce(
);
export const FT_history = collectionHistoryConfig.reduce(
(acc: any, collection) => {
(acc: any, collection: any) => {
return {
...acc,
[collection.name
@@ -68,7 +69,7 @@ export const FT_history = collectionHistoryConfig.reduce(
);
export const FT_permissions = permissionsConfig.reduce(
(acc: any, collection) => {
(acc: any, collection: any) => {
return {
...acc,
[collection.name]: permissionControlFnsGenerator(collection),
@@ -76,12 +77,3 @@ export const FT_permissions = permissionsConfig.reduce(
},
{}
);
export const FT_synonyms = synonymsConfig.reduce((acc: any, collection) => {
return {
...acc,
[collection.name
.replace(/\//g, "_")
.replace(/_{.*?}_/g, "_")]: synonymsFnsGenerator(collection),
};
}, {});

View File

@@ -1 +1 @@
[{ "name": "userPermissions", "customTokenFields": ["regions", "roles"] }]
[]

View File

@@ -1,265 +0,0 @@
import * as algoliasearch from "algoliasearch";
import { env } from "../config";
import * as _ from "lodash";
const client = algoliasearch(env.algolia.app, env.algolia.key);
const generateAlgoliaKey = (fieldName: string, value: string) =>
client.generateSecuredApiKey(
env.algolia.search, // Make sure to use a search key
{
filters: `${fieldName}:${value}`,
}
);
const cohort2algoliaKey = (cohort: string) =>
generateAlgoliaKey("cohort", cohort);
const tableConnect2ids = records => {
if (records) {
return records.map(r => r.snapshot.objectID);
} else {
return [];
}
};
const cohort2region = (cohort: string) =>
cohort.toUpperCase().replace(/\d+.*$/, "");
const locations = [
{ location: "Sydney", region: "SYD" },
{ location: "Singapore", region: "SG" },
{ location: "Amsterdam", region: "AMS" },
{ location: "Oslo", region: "OSL" },
{ location: "New York", region: "NYC" },
{ location: "London", region: "LON" },
{ location: "Nairobi", region: "NAI" },
{ location: "Stockholm", region: "STO" },
];
const location2region = (location: string) => {
const _location = _.find(locations, { location });
return _location ? _location.region : "GLOBAL";
};
const cohort2location = (cohort: string) => {
const _region = cohort2region(cohort);
switch (_region) {
case "SYD":
return "Sydney";
case "SG":
return "Singapore";
case "AMS":
return "Amsterdam";
case "OSL":
return "Oslo";
case "LON":
return "London";
case "STO":
return "Stockholm";
case "NYC":
return "New York";
case "NAI":
return "Nairobi";
default:
return "";
}
};
const cohort2regionCollections = (collections: string[]) =>
collections.map(collection => ({
name: collection,
groups: [
{
listenerField: "cohort",
synonymField: "region",
transformer: cohort2region,
},
],
}));
const config = [
{
name: "advisors",
groups: [
{
listenerField: "location",
synonymField: "region",
transformer: location2region,
},
],
},
{
name: "icManagement/{icId}/icMembers/{memberId}/votes",
groups: [
{
listenerField: "team",
synonymField: "teamName",
transformer: team => (team[0] ? team[0].teamName : ""),
},
],
},
{
name: "cohorts",
groups: [
{
listenerField: "cohort",
synonymField: "region",
transformer: cohort2region,
},
{
listenerField: "cohort",
synonymField: "algoliaKey",
transformer: (cohort: string) =>
client.generateSecuredApiKey(
env.algolia.search, // Make sure to use a search key
{
filters: `cohort:${cohort} OR cohort:Global`,
restrictIndices: [
"teams",
"founders",
"students",
"hubResources",
"advisors",
"partnerships",
],
}
),
},
{
listenerField: "cohort",
synonymField: "demoDayAlgoliaKey",
transformer: (cohort: string) =>
client.generateSecuredApiKey(
env.algolia.search, // Make sure to use a search key
{
filters: `cohort:${cohort} AND showOnDemoDayWebsite:true`,
restrictIndices: ["portfolio"],
}
),
},
{
listenerField: "location",
synonymField: "demoDayAntlerBiosAlgoliaKey",
transformer: (location: string) =>
client.generateSecuredApiKey(
env.algolia.search, // Make sure to use a search key
{
filters: `location:"${location}"`,
restrictIndices: ["antlerBios"],
}
),
},
],
},
{
name: "icManagement",
groups: [
{
listenerField: "cohort",
synonymField: "region",
transformer: cohort2region,
},
{
listenerField: "cohort",
synonymField: "icPage",
transformer: (cohort, doc) => {
if (doc.icType === "IC" || doc.icType === "Delayed IC") {
return `https://firepage.antler.co/IC/${cohort}`;
} else return "";
},
},
{
listenerField: "cohort",
synonymField: "algoliaKey",
transformer: cohort2algoliaKey,
},
],
},
{
name: "portfolio",
groups: [
{
listenerField: "cohort",
synonymField: "region",
transformer: cohort2region,
},
{
listenerField: "cohort",
synonymField: "location",
transformer: cohort2location,
},
{
listenerField: "id",
synonymField: "onePager",
transformer: id => `https://firepage.antler.co/portfolio/${id}`,
},
{
listenerField: "coach",
synonymField: "coachID",
transformer: tableConnect2ids,
},
],
},
{
name: "teams",
groups: [
{
listenerField: "coach",
synonymField: "coachID",
transformer: tableConnect2ids,
},
{
listenerField: "cohort",
synonymField: "region",
transformer: cohort2region,
},
{
listenerField: "id",
synonymField: "ddPage",
transformer: id => `https://firepage.antler.co/DD/${id}`,
},
{
isForced: true,
listenerField: "icResult",
synonymField: "isDecided",
transformer: icResult => {
if (icResult && ["Yes", "No"].includes(icResult)) return true;
else return false;
},
},
],
},
{
name: "profiles",
groups: [
{
listenerField: "email",
synonymField: "isInternal",
transformer: email => email.includes("@antler.co"),
},
{
listenerField: "cohort",
synonymField: "region",
transformer: cohort2region,
},
],
},
{
name: "demodays",
groups: [
{
listenerField: "cohortDoc",
synonymField: "cohort",
transformer: cohortDoc => cohortDoc?.[0]?.snapshot?.cohort,
},
],
},
...cohort2regionCollections([
"hubResources",
"sprintSubmissions",
"trackoutApplications",
"portfolioEnquiries",
]),
];
export default config;

View File

@@ -1,95 +0,0 @@
import * as functions from "firebase-functions";
import { db } from "../config";
type synonymGroup = {
isForced: boolean | undefined;
listenerField: string;
synonymField: string;
transformer: Function;
};
const synonyms = (docData, groups: synonymGroup[]) =>
groups.reduce(async (acc: any, currGroup) => {
const newValue = await currGroup.transformer(
docData[currGroup.listenerField],
docData
);
if (
docData[currGroup.listenerField] &&
docData[currGroup.synonymField] !== newValue
) {
return {
...(await acc),
[currGroup.synonymField]: newValue,
};
} else return await acc;
}, {});
/**
*
*/
const addSynonymOnUpdate = (groups: synonymGroup[]) => async (
change: functions.Change<FirebaseFirestore.DocumentSnapshot>
) => {
const beforeData = change.before.data();
const afterData = change.after.data();
if (!beforeData || !afterData) {
return false;
}
const changedGroups = groups.reduce((acc: synonymGroup[], currGroup) => {
if (
currGroup.isForced ||
beforeData[currGroup.listenerField] !== afterData[currGroup.listenerField]
) {
return [...acc, currGroup];
} else {
return acc;
}
}, []);
if (changedGroups.length === 0) {
return false; // no changes detected
}
const updates = await synonyms(
{ ...afterData, id: change.after.id },
changedGroups
);
if (Object.values(updates).length === 0) {
return false;
} else {
const docPath = change.after.ref.path;
return db.doc(docPath).update(updates);
}
};
const addSynonymOnCreate = (groups: synonymGroup[]) => async (
snapshot: FirebaseFirestore.DocumentSnapshot
) => {
const docData = snapshot.data();
if (!docData) {
return false;
}
const updates = await synonyms({ ...docData, id: snapshot.id }, groups);
if (Object.keys(updates).length === 0) {
return false;
} else {
const docPath = snapshot.ref.path;
return db.doc(docPath).update(updates);
}
};
/**
*
* @param collection configuration object
*/
const synonymsFnsGenerator = collection => ({
onCreate: functions.firestore
.document(`${collection.name}/{docId}`)
.onCreate(addSynonymOnCreate(collection.groups)),
onUpdate: functions.firestore
.document(`${collection.name}/{docId}`)
.onUpdate(addSynonymOnUpdate(collection.groups)),
});
export default synonymsFnsGenerator;

View File

@@ -12,10 +12,14 @@ steps:
entrypoint: yarn
args: ["install"]
dir: "cloud_functions/functions"
- name: node:10.15.1
entrypoint: yarn
args: ["fetchConfig"]
dir: "cloud_functions/functions"
- name: node:10.15.1
entrypoint: yarn
args:
- "deploy"
- "deployFT"
- "--project"
- "${_PROJECT_ID}"
- "--token"

View File

@@ -1,5 +1,4 @@
import React, { Suspense } from "react";
import clsx from "clsx";
import { FormatterProps } from "react-data-grid";
import { makeStyles, createStyles } from "@material-ui/core";