From 8036a8ccd108d5ff2581c331b4991bc2f233acbd Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 24 Feb 2020 19:55:21 +0000 Subject: [PATCH 1/7] remove additional profile creation method --- src/stores/User/user.store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/User/user.store.ts b/src/stores/User/user.store.ts index ad3ae61b44..3bec19278c 100644 --- a/src/stores/User/user.store.ts +++ b/src/stores/User/user.store.ts @@ -93,7 +93,7 @@ export class UserStore extends ModuleStore { if (userMeta) { this.updateUser(userMeta) } else { - this.createUserProfile() + throw new Error(`could not find user profile [${user.uid}]`) } } } From aacbe534cba4318910956f3feadb63946e238c26 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 24 Feb 2020 19:55:56 +0000 Subject: [PATCH 2/7] add user revision backup function --- functions/src/Firebase/userRevisions.ts | 22 ++++++++++++++++++++++ functions/src/index.ts | 2 ++ 2 files changed, 24 insertions(+) create mode 100644 functions/src/Firebase/userRevisions.ts diff --git a/functions/src/Firebase/userRevisions.ts b/functions/src/Firebase/userRevisions.ts new file mode 100644 index 0000000000..5b4283e18f --- /dev/null +++ b/functions/src/Firebase/userRevisions.ts @@ -0,0 +1,22 @@ +import * as functions from 'firebase-functions' +import { DBDoc, IDBEndpoint } from '../models' +const USERS_ENDPOINT: IDBEndpoint = 'v3_users' + +/** + * Automatically create user revision on update + * Nests revision as subcollection of original document, + * labeled by previous _modified timestamp + */ +export const FirestoreUserRevisions = functions.firestore + .document(`${USERS_ENDPOINT}/{username}`) + .onUpdate((change, context) => { + const { before, after } = change + const rev = before.data() as DBDoc + if (rev && rev._modified) { + return before.ref + .collection('revisions') + .doc(rev._modified) + .set(rev) + } + return null + }) diff --git a/functions/src/index.ts b/functions/src/index.ts index 387728f521..e9da68a1af 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -3,6 +3,7 @@ import { weeklyTasks, dailyTasks } from './exports/tasks' import { DH_Exports } from './DaveHakkensNL' import * as IntegrationsSlack from './Integrations/firebase-slack' import * as IntegrationsDiscord from './Integrations/firebase-discord' +import { FirestoreUserRevisions } from './Firebase/userRevisions' // the following endpoints are exposed for use by various triggers // see individual files for more informaiton @@ -18,3 +19,4 @@ exports.notifyNewEvent = IntegrationsSlack.notifyNewEvent exports.notifyPinAccepted = IntegrationsDiscord.notifyPinAccepted exports.notifyHowToAccepted = IntegrationsDiscord.notifyHowToAccepted exports.notifyEventAccepted = IntegrationsDiscord.notifyEventAccepted +exports.firestoreUserRevisions = FirestoreUserRevisions From 36e35ace5ed96d5eebc119fdd6a31399b49c909f Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 25 Feb 2020 10:59:18 +0000 Subject: [PATCH 3/7] add template firestore rules --- firestore.rules.WiP | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 firestore.rules.WiP diff --git a/firestore.rules.WiP b/firestore.rules.WiP new file mode 100644 index 0000000000..0d7bf80f32 --- /dev/null +++ b/firestore.rules.WiP @@ -0,0 +1,31 @@ +// WiP - CC - 2020-02-24 +// As part of future security update will make better use of firestore rules, as stubbed out below + +service cloud.firestore { + // rules will apply to all docs in database + match /databases/{database}/documents { + // Function to check whether request user has specific permission set in user database + function hasUserRole(username,role){ + // TODO - look up a profile to see if the user has specific role (e.g. admin) + // NOTE - this is not currently possible as the username is not sent with a request + // but could be made possible by setting the firebase auth name to the username + // and ensuring all users had a userRoles property + // NOTE 2 - possible code below but would need testing (https://firebase.google.com/docs/reference/rules/rules.List) + // return role in get(/databases/$(database)/documents/v3_users/$(username)).data.userRoles + return true + } + // Function to check if request to modify a document is by the auth owner + function isSameUser(username){ + return get(/databases/$(database)/documents/v3_users/$(username)).data._authID==request.auth.uid + } + match /v3_users/{username} { + allow read, write: if hasUserRole('admin') + allow read,write + } + // users can read/write their own user docs + match /v3_users/{username} { + allow read, write: if isSameUser(username) + } + } +} + From 92731d88afcdbd89c736fc515f4dfd75749714bc Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 25 Feb 2020 14:16:51 +0000 Subject: [PATCH 4/7] remove deprecated analytics code --- functions/src/Analytics/analytics.ts | 54 ------------------------- functions/src/Analytics/comments.ts | 16 -------- src/stores/Analytics/analytics.store.ts | 43 -------------------- 3 files changed, 113 deletions(-) delete mode 100644 functions/src/Analytics/analytics.ts delete mode 100644 functions/src/Analytics/comments.ts delete mode 100644 src/stores/Analytics/analytics.store.ts diff --git a/functions/src/Analytics/analytics.ts b/functions/src/Analytics/analytics.ts deleted file mode 100644 index f38206a3e4..0000000000 --- a/functions/src/Analytics/analytics.ts +++ /dev/null @@ -1,54 +0,0 @@ -import Axios from 'axios' - -export const getAnalyticsReport = async ( - viewId: string, - accessToken: string, -) => { - console.log('getting analytics report', viewId, accessToken) - const result = await Axios({ - url: 'https://analyticsreporting.googleapis.com/v4/reports:batchGet', - method: 'POST', - headers: { - Authorization: `Bearer ${accessToken}`, - }, - data: { - reportRequests: [ - { - viewId: viewId, - dateRanges: [ - { - startDate: '2019-01-01', - endDate: 'today', - }, - ], - metrics: [ - { - expression: 'ga:pageviews', - }, - ], - dimensions: [ - { - name: 'ga:pagePath', - }, - ], - dimensionFilterClauses: [ - { - filters: [ - { - dimensionName: 'ga:pagePath', - operator: 'BEGINS_WITH', - expressions: ['/discussions/post/'], - }, - ], - }, - ], - }, - ], - }, - }).catch(err => { - console.log('analytics error', err) - return err - }) - console.log('result received', result) - return result.data.reports[0].data.rows -} diff --git a/functions/src/Analytics/comments.ts b/functions/src/Analytics/comments.ts deleted file mode 100644 index 8e62d630c3..0000000000 --- a/functions/src/Analytics/comments.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { db } from '../Firebase/firestoreDB' - -export const syncCommentsCount = async context => { - // ref to the parent document - const discussionId = context.params.discussionId - const docRef = db.collection('discussions').doc(discussionId) - - // get all comments and aggregate - return docRef - .collection('comments') - .get() - .then(querySnapshot => { - return docRef.update({ _commentCount: querySnapshot.size }) - }) - .catch(err => console.log(err)) -} diff --git a/src/stores/Analytics/analytics.store.ts b/src/stores/Analytics/analytics.store.ts deleted file mode 100644 index 9de1c05217..0000000000 --- a/src/stores/Analytics/analytics.store.ts +++ /dev/null @@ -1,43 +0,0 @@ -import ReactGA from 'react-ga' -// import { GOOGLE_ANALYTICS_CONFIG } from 'src/config/config' - -export class AnalyticsStore { - // constructor() { - // ReactGA.initialize(GOOGLE_ANALYTICS_CONFIG.trackingCode, { debug: true }) - // } - // public postViewReactGA(postId: string) { - // ReactGA.ga('send', 'pageview', '/discussions/post/' + postId) - // } - - // function to pull data from google analytics - // *** NOTE - currently broken (CORs) and requires move to server functions (see issue #320) - public async getAnalytics() { - // const credsRequest = await functions.httpsCallable('getAccessToken')({ - // accessScopes: [ - // 'https://www.googleapis.com/auth/analytics', - // 'https://www.googleapis.com/auth/analytics.readonly', - // ], - // }) - // console.log('creds request', credsRequest) - // const analyticsReportRequest = functions.httpsCallable('getAnalyticsReport') - // console.log('getting analytics') - // const analyticsReportRows = (await analyticsReportRequest({ - // viewId: GOOGLE_ANALYTICS_CONFIG.viewId, - // credentials: credsRequest.data.token, - // })) as any - // console.log('report rows', analyticsReportRows) - // const updatedPosts = this.state.posts - // if (analyticsReportRows) { - // for (const post of updatedPosts) { - // const postAnalytic = analyticsReportRows.find( - // row => row.dimensions[0] === `/discussions/post/${post._id}`, - // ) - // if (postAnalytic) { - // post.viewCount = Number(postAnalytic.metrics[0].values[0]) - // } else { - // post.viewCount = 0 - // } - // } - // } - } -} From cd00d7d7ebf3f9d9ca6a786d86ed086b1f13cd6f Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 25 Feb 2020 14:18:21 +0000 Subject: [PATCH 5/7] refactor user revision backend function --- functions/src/Firebase/userRevisions.ts | 22 ---------------------- functions/src/index.ts | 4 ++-- 2 files changed, 2 insertions(+), 24 deletions(-) delete mode 100644 functions/src/Firebase/userRevisions.ts diff --git a/functions/src/Firebase/userRevisions.ts b/functions/src/Firebase/userRevisions.ts deleted file mode 100644 index 5b4283e18f..0000000000 --- a/functions/src/Firebase/userRevisions.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as functions from 'firebase-functions' -import { DBDoc, IDBEndpoint } from '../models' -const USERS_ENDPOINT: IDBEndpoint = 'v3_users' - -/** - * Automatically create user revision on update - * Nests revision as subcollection of original document, - * labeled by previous _modified timestamp - */ -export const FirestoreUserRevisions = functions.firestore - .document(`${USERS_ENDPOINT}/{username}`) - .onUpdate((change, context) => { - const { before, after } = change - const rev = before.data() as DBDoc - if (rev && rev._modified) { - return before.ref - .collection('revisions') - .doc(rev._modified) - .set(rev) - } - return null - }) diff --git a/functions/src/index.ts b/functions/src/index.ts index e9da68a1af..29a94ccc85 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -3,7 +3,7 @@ import { weeklyTasks, dailyTasks } from './exports/tasks' import { DH_Exports } from './DaveHakkensNL' import * as IntegrationsSlack from './Integrations/firebase-slack' import * as IntegrationsDiscord from './Integrations/firebase-discord' -import { FirestoreUserRevisions } from './Firebase/userRevisions' +import { FirebaseUserBackup } from './Integrations/firebase-userBackup' // the following endpoints are exposed for use by various triggers // see individual files for more informaiton @@ -19,4 +19,4 @@ exports.notifyNewEvent = IntegrationsSlack.notifyNewEvent exports.notifyPinAccepted = IntegrationsDiscord.notifyPinAccepted exports.notifyHowToAccepted = IntegrationsDiscord.notifyHowToAccepted exports.notifyEventAccepted = IntegrationsDiscord.notifyEventAccepted -exports.firestoreUserRevisions = FirestoreUserRevisions +exports.firebaseUserBackup = FirebaseUserBackup From 6cc8973ac654339a0bcb46417efb4d8fe6fb912b Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 25 Feb 2020 14:18:37 +0000 Subject: [PATCH 6/7] refactor user backup --- .../src/Integrations/firebase-userBackup.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 functions/src/Integrations/firebase-userBackup.ts diff --git a/functions/src/Integrations/firebase-userBackup.ts b/functions/src/Integrations/firebase-userBackup.ts new file mode 100644 index 0000000000..7451e84eb2 --- /dev/null +++ b/functions/src/Integrations/firebase-userBackup.ts @@ -0,0 +1,22 @@ +import * as functions from 'firebase-functions' +import { DBDoc, IDBEndpoint } from '../models' +const USERS_ENDPOINT: IDBEndpoint = 'v3_users' + +/** + * Automatically create user revision on update + * Nests revision as subcollection of original document, + * labeled by previous _modified timestamp + */ +export const FirebaseUserBackup = functions.firestore + .document(`${USERS_ENDPOINT}/{username}`) + .onUpdate((change, context) => { + const { before, after } = change + const rev = before.data() as DBDoc + if (rev && rev._modified) { + return before.ref + .collection('revisions') + .doc(rev._modified) + .set(rev) + } + return null + }) From b94351b13cda4fc17d1629b8cc09625955410e80 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 25 Feb 2020 14:56:58 +0000 Subject: [PATCH 7/7] add backend email integration --- functions/src/Integrations/firebase-email.ts | 29 ++++++++++++++++++++ functions/src/index.ts | 2 ++ 2 files changed, 31 insertions(+) create mode 100644 functions/src/Integrations/firebase-email.ts diff --git a/functions/src/Integrations/firebase-email.ts b/functions/src/Integrations/firebase-email.ts new file mode 100644 index 0000000000..d9bce96840 --- /dev/null +++ b/functions/src/Integrations/firebase-email.ts @@ -0,0 +1,29 @@ +import * as functions from 'firebase-functions' +import { IDBEndpoint } from '../models' +import { db } from '..//Firebase/firestoreDB' + +const USER_ENDPOINT: IDBEndpoint = 'v3_users' + +/** + * Example function to show how an automated email can be triggered + * In this case it is being used temporarily to help debug + * https://github.com/ONEARMY/community-platform/issues/883 + */ +export const notifyEmailDemo = functions.firestore + .document(`${USER_ENDPOINT}/precious-plastic`) + .onWrite(async (change, context) => { + return db.collection('mail').add({ + to: 'chris.m.clarke@live.co.uk', + message: { + subject: 'PP Profile Edited', + html: ` +

Just thought you should know that an edit has been made to your profile

+

Before

+ ${JSON.stringify(change.before.data())} +

After

+ ${JSON.stringify(change.after.data())} +

To see a clear breakdown of differences you could copy-paste each section into http://www.jsondiff.com

+ `, + }, + }) + }) diff --git a/functions/src/index.ts b/functions/src/index.ts index 29a94ccc85..ff3c29061c 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -4,6 +4,7 @@ import { DH_Exports } from './DaveHakkensNL' import * as IntegrationsSlack from './Integrations/firebase-slack' import * as IntegrationsDiscord from './Integrations/firebase-discord' import { FirebaseUserBackup } from './Integrations/firebase-userBackup' +import * as IntegrationsEmail from './Integrations/firebase-email' // the following endpoints are exposed for use by various triggers // see individual files for more informaiton @@ -20,3 +21,4 @@ exports.notifyPinAccepted = IntegrationsDiscord.notifyPinAccepted exports.notifyHowToAccepted = IntegrationsDiscord.notifyHowToAccepted exports.notifyEventAccepted = IntegrationsDiscord.notifyEventAccepted exports.firebaseUserBackup = FirebaseUserBackup +exports.emailNotificationDemo = IntegrationsEmail.notifyEmailDemo