diff --git a/functions/firebase/.gcloudignore b/functions/firebase/.gcloudignore new file mode 100644 index 0000000000..ccc4eb240e --- /dev/null +++ b/functions/firebase/.gcloudignore @@ -0,0 +1,16 @@ +# This file specifies files that are *not* uploaded to Google Cloud Platform +# using gcloud. It follows the same syntax as .gitignore, with the addition of +# "#!include" directives (which insert the entries of the given .gitignore-style +# file at that point). +# +# For more information, run: +# $ gcloud topic gcloudignore +# +.gcloudignore +# If you would like to upload your .git directory, .gitignore file or files +# from your .gitignore file, remove the corresponding line +# below: +.git +.gitignore + +node_modules diff --git a/functions/firebase/index.js b/functions/firebase/index.js index 15a158c039..96adf500fa 100644 --- a/functions/firebase/index.js +++ b/functions/firebase/index.js @@ -84,3 +84,25 @@ exports.helloAuth = (event, callback) => { callback(); }; // [END functions_firebase_auth] + +// [START functions_firebase_reactive] +const Firestore = require('@google-cloud/firestore'); + +const firestore = new Firestore({ + projectId: process.env.GCP_PROJECT +}); + +// Converts strings added to /messages/{pushId}/original to uppercase +exports.makeUpperCase = (event, callback) => { + const resource = event.resource; + const affectedDoc = firestore.doc(resource.split('/documents/')[1]); + + const curValue = event.data.value.fields.original.stringValue; + const newValue = curValue.toUpperCase(); + console.log(`Replacing value: ${curValue} --> ${newValue}`); + + return affectedDoc.set({ + 'original': newValue + }); +}; +// [END functions_firebase_reactive] diff --git a/functions/firebase/package.json b/functions/firebase/package.json new file mode 100644 index 0000000000..eb97e5d156 --- /dev/null +++ b/functions/firebase/package.json @@ -0,0 +1,30 @@ +{ + "name": "nodejs-docs-samples-functions-firebase", + "version": "0.0.1", + "private": true, + "license": "Apache-2.0", + "author": "Google Inc.", + "repository": { + "type": "git", + "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git" + }, + "engines": { + "node": ">=4.3.2" + }, + "scripts": { + "pretest": "repo-tools lint", + "test": "ava -T 30s test/*.test.js" + }, + "devDependencies": { + "@google-cloud/nodejs-repo-tools": "^2.2.5", + "ava": "0.25.0", + "proxyquire": "2.0.1", + "semistandard": "^12.0.1", + "sinon": "4.4.8", + "supertest": "^3.0.0", + "uuid": "^3.1.0" + }, + "dependencies": { + "@google-cloud/firestore": "^0.17.0" + } +} diff --git a/functions/firebase/test/index.test.js b/functions/firebase/test/index.test.js new file mode 100644 index 0000000000..c085331eb3 --- /dev/null +++ b/functions/firebase/test/index.test.js @@ -0,0 +1,143 @@ +/** + * Copyright 2018, Google LLC. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const proxyquire = require(`proxyquire`).noCallThru(); +const sinon = require(`sinon`); +const test = require(`ava`); +const tools = require(`@google-cloud/nodejs-repo-tools`); + +function getSample () { + const firestoreMock = { + doc: sinon.stub().returnsThis(), + set: sinon.stub() + }; + + return { + program: proxyquire(`../`, { + '@google-cloud/firestore': sinon.stub().returns(firestoreMock) + }), + mocks: { + firestore: firestoreMock + } + }; +} + +test.beforeEach(tools.stubConsole); +test.afterEach.always(tools.restoreConsole); + +test(`should listen to RTDB`, t => { + const sample = getSample(); + + const delta = { + foo: 'bar' + }; + const event = { + resource: 'resource', + auth: { + admin: true + }, + delta: delta + }; + const cb = sinon.stub(); + + sample.program.helloRTDB(event, cb); + + t.true(console.log.calledWith(`Function triggered by change to: resource`)); + t.true(console.log.calledWith(`Admin?: true`)); + t.true(console.log.calledWith(JSON.stringify(delta, null, 2))); + t.true(cb.calledOnce); +}); + +test(`should listen to Firestore`, t => { + const sample = getSample(); + + const oldValue = { + foo: 'bar' + }; + const value = { + bar: 'baz' + }; + const event = { + resource: 'resource', + eventType: 'type', + data: { + oldValue: oldValue, + value: value + } + }; + const cb = sinon.stub(); + + sample.program.helloFirestore(event, cb); + + t.true(console.log.calledWith(`Function triggered by event on: resource`)); + t.true(console.log.calledWith(`Event type: type`)); + t.true(console.log.calledWith(JSON.stringify(oldValue, null, 2))); + t.true(console.log.calledWith(JSON.stringify(value, null, 2))); + t.true(cb.calledOnce); +}); + +test(`should listen to Auth events`, t => { + const sample = getSample(); + const date = Date.now(); + const event = { + resource: 'resource', + data: { + uid: 'me', + email: 'me@example.com', + metadata: { + createdAt: date + } + } + }; + const cb = sinon.stub(); + + sample.program.helloAuth(event, cb); + + t.true(console.log.calledWith(`Function triggered by change to user: me`)); + t.true(console.log.calledWith(`Created at: ${date}`)); + t.true(console.log.calledWith(`Email: me@example.com`)); + t.true(cb.calledOnce); +}); + +test(`should update data in response to Firestore events`, t => { + const sample = getSample(); + + const date = Date.now(); + const event = { + resource: '/documents/some/path', + data: { + email: 'me@example.com', + metadata: { + createdAt: date + }, + value: { + fields: { + original: { + stringValue: 'foobar' + } + } + } + } + }; + const cb = sinon.stub(); + + sample.program.makeUpperCase(event, cb); + + t.true(sample.mocks.firestore.doc.calledWith('some/path')); + t.true(console.log.calledWith(`Replacing value: foobar --> FOOBAR`)); + t.true(sample.mocks.firestore.set.calledWith({'original': 'FOOBAR'})); +});