├── .eslintignore ├── .eslintrc.yml ├── .github ├── CODEOWNERS ├── auto-approve.yml ├── blunderbuss.yml ├── snippet-bot.yml ├── sync-repo-settings.yaml └── trusted-contribution.yml ├── .gitignore ├── .mailmap ├── .prettier ├── .prettierignore ├── .prettierrc ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── authenticating-users ├── .eslintrc.yml ├── README.md ├── app.js ├── app.yaml ├── package.json └── test │ └── app.test.js ├── background ├── README.md ├── function │ ├── package.json │ └── translate.js ├── package.json ├── server │ ├── app.js │ ├── app.yaml │ ├── index.html │ └── package.json └── test │ ├── app.test.js │ └── testApp │ ├── package.json │ └── translate.js ├── bookshelf ├── .eslintignore ├── .gcloudignore ├── .gitignore ├── Dockerfile ├── README.md ├── app.js ├── app.yaml ├── books │ ├── api.js │ ├── crud.js │ └── firestore.js ├── lib │ └── images.js ├── package.json ├── test │ └── app.test.js └── views │ ├── base.pug │ └── books │ ├── form.pug │ ├── list.pug │ └── view.pug ├── ci ├── .mocharc.js └── cloudbuild │ ├── Dockerfile │ ├── cloudbuild.yaml │ ├── create_build_triggers.sh │ ├── export │ ├── gcb-continuous-node14.yaml │ ├── gcb-nightly-node14.yaml │ └── gcb-presubmit-node14.yaml │ ├── export_triggers.sh │ ├── import_triggers.sh │ ├── run_single_test.sh │ └── run_test.sh ├── gce ├── .gitignore ├── README.md ├── app.js ├── package.json ├── startup-script.sh └── test │ └── app.test.js ├── package.json ├── renovate.json └── sessions ├── app.yaml ├── index.js ├── package.json └── test └── index.test.js /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/coverage 3 | test/fixtures 4 | build/ 5 | docs/ 6 | protos/ 7 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | extends: 3 | - 'eslint:recommended' 4 | - 'plugin:node/recommended' 5 | - prettier 6 | env: 7 | mocha: true 8 | plugins: 9 | - node 10 | - prettier 11 | rules: 12 | prettier/prettier: error 13 | block-scoped-var: error 14 | eqeqeq: error 15 | no-warning-comments: warn 16 | no-console: off 17 | node/no-missing-require: off 18 | node/no-unpublished-require: off 19 | 20 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Code owners file. 2 | # This file controls who is tagged for review for any given pull request. 3 | # 4 | # For syntax help see: 5 | # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax 6 | 7 | 8 | # The yoshi-nodejs team is the default owner for nodejs repositories. 9 | * @googleapis/yoshi-nodejs 10 | 11 | # The github automation team is the default owner for the auto-approve file. 12 | .github/auto-approve.yml @googleapis/github-automation -------------------------------------------------------------------------------- /.github/auto-approve.yml: -------------------------------------------------------------------------------- 1 | rules: 2 | - author: "renovate-bot" 3 | title: "^(fix|chore)\\(deps\\):" 4 | changedFiles: 5 | - "package\\.json$" 6 | maxFiles: 2 -------------------------------------------------------------------------------- /.github/blunderbuss.yml: -------------------------------------------------------------------------------- 1 | # See https://github.com/googleapis/repo-automation-bots/tree/main/packages/blunderbuss 2 | 3 | assign_issues: 4 | - alexander-fenster 5 | - bcoe 6 | - sofisl 7 | - summer-ji-eng 8 | 9 | assign_prs: 10 | - alexander-fenster 11 | - bcoe 12 | - sofisl 13 | - summer-ji-eng 14 | -------------------------------------------------------------------------------- /.github/snippet-bot.yml: -------------------------------------------------------------------------------- 1 | # Empty snippet-bot config file 2 | -------------------------------------------------------------------------------- /.github/sync-repo-settings.yaml: -------------------------------------------------------------------------------- 1 | rebaseMergeAllowed: true 2 | squashMergeAllowed: true 3 | mergeCommitAllowed: false 4 | branchProtectionRules: 5 | - pattern: main 6 | isAdminEnforced: true 7 | requiredStatusCheckContexts: 8 | - gcb-presubmit-node14 (firestore-nodejs-getting-start) 9 | - cla/google 10 | - snippet-bot check 11 | requiredApprovingReviewCount: 1 12 | requiresCodeOwnerReviews: true 13 | requiresStrictStatusChecks: true 14 | permissionRules: 15 | - team: Googlers 16 | permission: pull 17 | - team: nodejs-samples-reviewers 18 | permission: push 19 | - team: nodejs-docs-samples-owners 20 | permission: admin 21 | -------------------------------------------------------------------------------- /.github/trusted-contribution.yml: -------------------------------------------------------------------------------- 1 | annotations: 2 | - type: comment 3 | text: "/gcbrun" 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.gcloudignore 2 | .vscode 3 | **/*.log 4 | **/node_modules 5 | .coverage 6 | .nyc_output 7 | docs/ 8 | out/ 9 | build/ 10 | system-test/secrets.js 11 | system-test/*key.json 12 | *.lock 13 | .DS_Store 14 | google-cloud-logging-winston-*.tgz 15 | google-cloud-logging-bunyan-*.tgz 16 | **/coverage/* 17 | **/package-lock.json 18 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | JJ Geewax JJ Geewax 2 | Jason Dobry Jason Dobry 3 | Jason Dobry Jason Dobry 4 | Jun Mukai Jun Mukai 5 | Luke Sneeringer Luke Sneeringer 6 | Stephen Sawchuk Stephen 7 | Stephen Sawchuk Stephen Sawchuk 8 | Vikas Kedia Vikas Kedia 9 | Alexander Fenster Alexander Fenster 10 | Ali Ijaz Sheikh 11 | Austin Peterson 12 | Dave Gramlich 13 | Eric Uldall 14 | Ernest Landrito 15 | Justin King 16 | Jason Dobry Jason Dobry 17 | Jason Dobry Jason Dobry 18 | Karolis Narkevicius 19 | Kelvin Jin 20 | Luke Sneeringer Luke Sneeringer 21 | Matthew Loring 22 | Michael Prentice 23 | Stephen Sawchuk Stephen Sawchuk 24 | Stephen Sawchuk Stephen Sawchuk 25 | Stephen Sawchuk Stephen 26 | Tim Swast 27 | F. Hinkelmann Franziska Hinkelmann 28 | Jon Wayne Parrott Jon Wayne Parrott 29 | Jon Wayne Parrott Jonathan Wayne Parrott -------------------------------------------------------------------------------- /.prettier: -------------------------------------------------------------------------------- 1 | --- 2 | bracketSpacing: false 3 | printWidth: 80 4 | semi: true 5 | singleQuote: true 6 | tabWidth: 2 7 | trailingComma: es5 8 | useTabs: false 9 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/coverage 3 | test/fixtures 4 | build/ 5 | docs/ 6 | protos/ 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | --- 2 | bracketSpacing: false 3 | printWidth: 80 4 | semi: true 5 | singleQuote: true 6 | tabWidth: 2 7 | trailingComma: es5 8 | useTabs: false 9 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/about-codeowners/ 2 | # for more info about CODEOWNERS file 3 | 4 | # Repo owner 5 | * @sofisl 6 | * @fhinkel 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, 4 | and in the interest of fostering an open and welcoming community, 5 | we pledge to respect all people who contribute through reporting issues, 6 | posting feature requests, updating documentation, 7 | submitting pull requests or patches, and other activities. 8 | 9 | We are committed to making participation in this project 10 | a harassment-free experience for everyone, 11 | regardless of level of experience, gender, gender identity and expression, 12 | sexual orientation, disability, personal appearance, 13 | body size, race, ethnicity, age, religion, or nationality. 14 | 15 | Examples of unacceptable behavior by participants include: 16 | 17 | * The use of sexualized language or imagery 18 | * Personal attacks 19 | * Trolling or insulting/derogatory comments 20 | * Public or private harassment 21 | * Publishing other's private information, 22 | such as physical or electronic 23 | addresses, without explicit permission 24 | * Other unethical or unprofessional conduct. 25 | 26 | Project maintainers have the right and responsibility to remove, edit, or reject 27 | comments, commits, code, wiki edits, issues, and other contributions 28 | that are not aligned to this Code of Conduct. 29 | By adopting this Code of Conduct, 30 | project maintainers commit themselves to fairly and consistently 31 | applying these principles to every aspect of managing this project. 32 | Project maintainers who do not follow or enforce the Code of Conduct 33 | may be permanently removed from the project team. 34 | 35 | This code of conduct applies both within project spaces and in public spaces 36 | when an individual is representing the project or its community. 37 | 38 | Instances of abusive, harassing, or otherwise unacceptable behavior 39 | may be reported by opening an issue 40 | or contacting one or more of the project maintainers. 41 | 42 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, 43 | available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) 44 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | ## Contributor License Agreements 4 | 5 | We'd love to accept your sample apps and patches! Before we can take them, we 6 | have to jump a couple of legal hurdles. 7 | 8 | Please fill out either the individual or corporate Contributor License Agreement 9 | (CLA). 10 | 11 | * If you are an individual writing original source code and you're sure you 12 | own the intellectual property, then you'll need to sign an [individual CLA] 13 | (https://developers.google.com/open-source/cla/individual). 14 | * If you work for a company that wants to allow you to contribute your work, 15 | then you'll need to sign a [corporate CLA] 16 | (https://developers.google.com/open-source/cla/corporate). 17 | 18 | Follow either of the two links above to access the appropriate CLA and 19 | instructions for how to sign and return it. Once we receive it, we'll be able to 20 | accept your pull requests. 21 | 22 | ## Contributing A Patch 23 | 24 | 1. Submit an issue describing your proposed change to the repo in question. 25 | 1. The repo owner will respond to your issue promptly. 26 | 1. If your proposed change is accepted, and you haven't already done so, sign a 27 | Contributor License Agreement (see details above). 28 | 1. Fork the desired repo, develop and test your code changes. 29 | 1. Ensure that your code adheres to the existing style in the sample to which 30 | you are contributing. Refer to the 31 | [Google Cloud Platform Samples Style Guide] 32 | (https://github.com/GoogleCloudPlatform/Template/wiki/style.html) for the 33 | recommended coding standards for this organization. 34 | 1. Ensure that your code has an appropriate set of unit tests which all pass. 35 | 1. Submit a pull request. 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting started with Node.js on Google Cloud Platform 2 | 3 | This repository contains the complete sample code for the 4 | [Node.js Getting Started on Google Cloud Platform][getting-started] tutorials. 5 | Please refer to the tutorials for instructions on configuring, running, and 6 | deploying these samples. 7 | 8 | The code for each tutorial is in an individual folder in this repository. 9 | 10 | ## Contributing changes 11 | 12 | * See [CONTRIBUTING.md](CONTRIBUTING.md) 13 | 14 | ### Run the tests 15 | 16 | * Make sure you're authenticated with the `gcloud` SDK and your GCP project 17 | has enabled all the APIs used by these tutorials. 18 | * Make sure you've got the required environment variables set. 19 | * Replace below with the directory you want to perform tests in. 20 | ``` 21 | git clone git@github.com:GoogleCloudPlatform/nodejs-getting-started.git 22 | cd nodejs-getting-started 23 | cd 24 | npm install 25 | npm test 26 | ``` 27 | 28 | ## Licensing 29 | 30 | * See [LICENSE](LICENSE) -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | To report a security issue, please use [g.co/vulnz](https://g.co/vulnz). 4 | 5 | The Google Security Team will respond within 5 working days of your report on g.co/vulnz. 6 | 7 | We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue. 8 | -------------------------------------------------------------------------------- /authenticating-users/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | rules: 3 | require-atomic-updates: off -------------------------------------------------------------------------------- /authenticating-users/README.md: -------------------------------------------------------------------------------- 1 | # Authenticating users 2 | 3 | This folder contains the sample code for the *Authenticating users* 4 | tutorial. Please refer to the tutorial for instructions on configuring, running, 5 | and deploying this sample. 6 | -------------------------------------------------------------------------------- /authenticating-users/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019, Google LLC 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | 'use strict'; 17 | 18 | // [START getting_started_auth_all] 19 | const express = require('express'); 20 | const metadata = require('gcp-metadata'); 21 | const {OAuth2Client} = require('google-auth-library'); 22 | 23 | const app = express(); 24 | const oAuth2Client = new OAuth2Client(); 25 | 26 | // Cache externally fetched information for future invocations 27 | let aud; 28 | 29 | // [START getting_started_auth_metadata] 30 | async function audience() { 31 | if (!aud && (await metadata.isAvailable())) { 32 | let project_number = await metadata.project('numeric-project-id'); 33 | let project_id = await metadata.project('project-id'); 34 | 35 | aud = '/projects/' + project_number + '/apps/' + project_id; 36 | } 37 | 38 | return aud; 39 | } 40 | // [END getting_started_auth_metadata] 41 | 42 | // [START getting_started_auth_audience] 43 | async function validateAssertion(assertion) { 44 | if (!assertion) { 45 | return {}; 46 | } 47 | 48 | // Check that the assertion's audience matches ours 49 | const aud = await audience(); 50 | 51 | // Fetch the current certificates and verify the signature on the assertion 52 | // [START getting_started_auth_certs] 53 | const response = await oAuth2Client.getIapPublicKeys(); 54 | // [END getting_started_auth_certs] 55 | const ticket = await oAuth2Client.verifySignedJwtWithCertsAsync( 56 | assertion, 57 | response.pubkeys, 58 | aud, 59 | ['https://cloud.google.com/iap'] 60 | ); 61 | const payload = ticket.getPayload(); 62 | 63 | // Return the two relevant pieces of information 64 | return { 65 | email: payload.email, 66 | sub: payload.sub, 67 | }; 68 | } 69 | // [END getting_started_auth_audience] 70 | 71 | // [START getting_started_auth_front_controller] 72 | app.get('/', async (req, res) => { 73 | const assertion = req.header('X-Goog-IAP-JWT-Assertion'); 74 | let email = 'None'; 75 | try { 76 | const info = await validateAssertion(assertion); 77 | email = info.email; 78 | } catch (error) { 79 | console.log(error); 80 | } 81 | res.status(200).send(`Hello ${email}`).end(); 82 | }); 83 | 84 | // [END getting_started_auth_front_controller] 85 | 86 | // Start the server 87 | const PORT = process.env.PORT || 8080; 88 | app.listen(PORT, () => { 89 | console.log(`App listening on port ${PORT}`); 90 | console.log('Press Ctrl+C to quit.'); 91 | }); 92 | 93 | // [END getting_started_auth_all] 94 | 95 | module.exports = app; 96 | -------------------------------------------------------------------------------- /authenticating-users/app.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Google LLC 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | runtime: nodejs10 15 | -------------------------------------------------------------------------------- /authenticating-users/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iap-authentication", 3 | "description": "Minimal app to use authentication information from IAP.", 4 | "private": true, 5 | "license": "Apache-2.0", 6 | "author": "Google LLC", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/GoogleCloudPlatform/getting-started-nodejs.git" 10 | }, 11 | "engines": { 12 | "node": ">=12.0.0" 13 | }, 14 | "scripts": { 15 | "start": "node app.js", 16 | "test": "mocha --exit test/*.test.js" 17 | }, 18 | "dependencies": { 19 | "express": "^4.17.1", 20 | "gcp-metadata": "^5.0.0", 21 | "google-auth-library": "^8.0.0" 22 | }, 23 | "devDependencies": { 24 | "mocha": "^9.0.0", 25 | "supertest": "^6.0.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /authenticating-users/test/app.test.js: -------------------------------------------------------------------------------- 1 | const app = require('../app'); 2 | 3 | const request = require('supertest'); 4 | 5 | describe('GET /', () => { 6 | it('should get 200', (done) => { 7 | request(app).get('/').expect(200, done); 8 | }), 9 | it('should get Hello None', (done) => { 10 | request(app).get('/').expect('Hello undefined', done); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /background/README.md: -------------------------------------------------------------------------------- 1 | # Background Processing 2 | 3 | This directory contains an example of doing background processing with App Engine, Cloud Pub/Sub, Cloud Functions, and Firestore. -------------------------------------------------------------------------------- /background/function/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "background", 3 | "private": true, 4 | "description": "This directory contains an example of a Cloud Function.", 5 | "main": "translate.js", 6 | "author": "Google LLC", 7 | "license": "Apache-2.0", 8 | "scripts": { 9 | "test": "mocha --exit test/*.test.js" 10 | }, 11 | "dependencies": { 12 | "@google-cloud/firestore": "^5.0.0", 13 | "@google-cloud/translate": "^8.0.0" 14 | }, 15 | "devDependencies": {} 16 | } 17 | -------------------------------------------------------------------------------- /background/function/translate.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // This file contains an async Cloud Function, translate, to translate text. 16 | // The function listens to Pub/Sub, does the translations, and stores the 17 | // result in Firestore. 18 | 19 | // [START getting_started_background_translate_init] 20 | const Firestore = require('@google-cloud/firestore'); 21 | const {Translate} = require('@google-cloud/translate').v2; 22 | 23 | const firestore = new Firestore(); 24 | const translate = new Translate(); 25 | // [END getting_started_background_translate_init] 26 | 27 | // [START getting_started_background_translate] 28 | // translate translates the given message and stores the result in Firestore. 29 | // Triggered by Pub/Sub message. 30 | exports.translate = async (pubSubEvent) => { 31 | const {language, original} = JSON.parse( 32 | Buffer.from(pubSubEvent.data, 'base64').toString() 33 | ); 34 | 35 | // [START getting_started_background_translate_string] 36 | const [ 37 | translated, 38 | { 39 | data: {translations}, 40 | }, 41 | ] = await translate.translate(original, language); 42 | const originalLanguage = translations[0].detectedSourceLanguage; 43 | console.log( 44 | `Translated ${original} in ${originalLanguage} to ${translated} in ${language}.` 45 | ); 46 | // [END getting_started_background_translate_string] 47 | 48 | // Store translation in firestore. 49 | await firestore.collection('translations').doc().set({ 50 | language, 51 | original, 52 | translated, 53 | originalLanguage, 54 | }); 55 | }; 56 | // [END getting_started_background_translate] 57 | -------------------------------------------------------------------------------- /background/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-getting-started", 3 | "description": "End to end sample for running Node.js applications on Google Cloud Platform", 4 | "license": "Apache-2.0", 5 | "author": "Google LLC", 6 | "engines": { 7 | "node": ">=12" 8 | }, 9 | "repository": "https://github.com/GoogleCloudPlatform/nodejs-getting-started", 10 | "main": "app.js", 11 | "private": true, 12 | "scripts": { 13 | "start": "node server/app.js", 14 | "test": "mocha --exit test/app.test.js --timeout=50000" 15 | }, 16 | "dependencies": { 17 | "@google-cloud/firestore": "^5.0.0", 18 | "@google-cloud/pubsub": "3.1.0", 19 | "@google-cloud/storage": "^6.0.0", 20 | "express": "^4.16.4" 21 | }, 22 | "devDependencies": { 23 | "body-parser": "^1.18.3", 24 | "child_process": "^1.0.2", 25 | "@google-cloud/functions": "^3.0.0", 26 | "mocha": "^9.0.0", 27 | "node-fetch": "^2.6.0", 28 | "uuid": "^9.0.0", 29 | "wait-on": "^7.0.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /background/server/app.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // [START getting_started_background_app_main] 16 | 17 | // This app is an HTTP app that displays all previous translations 18 | // (stored in Firestore) and has a form to request new translations. On form 19 | // submission, the request is sent to Pub/Sub to be processed in the background. 20 | 21 | // TOPIC_NAME is the Pub/Sub topic to publish requests to. The Cloud Function to 22 | // process translation requests should be subscribed to this topic. 23 | const TOPIC_NAME = 'translate'; 24 | 25 | const express = require('express'); 26 | const bodyParser = require('body-parser'); 27 | const {PubSub} = require('@google-cloud/pubsub'); 28 | const {Firestore} = require('@google-cloud/firestore'); 29 | 30 | const app = express(); 31 | const port = process.env.PORT || 8080; 32 | 33 | const firestore = new Firestore(); 34 | 35 | const pubsub = new PubSub(); 36 | const topic = pubsub.topic(TOPIC_NAME); 37 | 38 | // Use handlebars.js for templating. 39 | app.set('views', __dirname); 40 | app.set('view engine', 'html'); 41 | app.engine('html', require('hbs').__express); 42 | 43 | app.use(bodyParser.urlencoded({extended: true})); 44 | 45 | app.get('/', index); 46 | app.post('/request-translation', requestTranslation); 47 | app.listen(port, () => console.log(`Listening on port ${port}!`)); 48 | 49 | // [END getting_started_background_app_main] 50 | 51 | // [START getting_started_background_app_list] 52 | 53 | // index lists the current translations. 54 | async function index(req, res) { 55 | const translations = []; 56 | const querySnapshot = await firestore.collection('translations').get(); 57 | querySnapshot.forEach((doc) => { 58 | console.log(doc.id, ' => ', doc.data()); 59 | translations.push(doc.data()); 60 | }); 61 | 62 | res.render('index', {translations}); 63 | } 64 | 65 | // [END getting_started_background_app_list] 66 | 67 | // [START getting_started_background_app_request] 68 | 69 | // requestTranslation parses the request, validates it, and sends it to Pub/Sub. 70 | function requestTranslation(req, res) { 71 | const language = req.body.lang; 72 | const original = req.body.v; 73 | 74 | const acceptableLanguages = ['de', 'en', 'es', 'fr', 'ja', 'sw']; 75 | if (!acceptableLanguages.includes(language)) { 76 | throw new Error(`Invalid language ${language}`); 77 | } 78 | 79 | console.log(`Translation requested: ${original} -> ${language}`); 80 | 81 | const buffer = Buffer.from(JSON.stringify({language, original})); 82 | topic.publish(buffer); 83 | res.sendStatus(200); 84 | } 85 | // [END getting_started_background_app_request] 86 | module.exports = app; 87 | -------------------------------------------------------------------------------- /background/server/app.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # [START getting_started_background_config] 15 | runtime: nodejs10 16 | # [END getting_started_background_config] 17 | 18 | service: testservice 19 | -------------------------------------------------------------------------------- /background/server/index.html: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Translations 20 | 21 | 22 | 23 | 24 | 25 | 61 | 69 | 70 | 71 | 72 | 73 |
74 |
75 |
76 | 77 | Translate with Background Processing 78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | 88 | 89 |
90 | 98 | 100 |
101 |
102 |
103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | {{#each translations}} 112 | 113 | 119 | 125 | 126 | {{/each}} 127 | 128 |
OriginalTranslation
114 | 115 | {{ originalLanguage }} 116 | 117 | {{ original }} 118 | 120 | 121 | {{ language }} 122 | 123 | {{ translated }} 124 |
129 |
130 | 133 |
134 |
135 |
136 |
137 |
138 | 139 |
140 |
141 |
142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /background/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "background", 3 | "private": true, 4 | "description": "This directory contains an example of doing background processing with App Engine, Cloud Pub/Sub, Cloud Functions, and Firestore.", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "node app.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "Google LLC", 11 | "license": "Apache-2.0", 12 | "dependencies": { 13 | "@google-cloud/firestore": "^5.0.0", 14 | "@google-cloud/pubsub": "^3.0.0", 15 | "body-parser": "^1.19.0", 16 | "express": "^4.17.1", 17 | "handlebars": "^4.2.0", 18 | "hbs": "^4.0.4" 19 | }, 20 | "devDependencies": {} 21 | } 22 | -------------------------------------------------------------------------------- /background/test/app.test.js: -------------------------------------------------------------------------------- 1 | const cp = require('child_process'); 2 | const path = require('path'); 3 | const projectId = process.env.GCLOUD_PROJECT; 4 | const regionId = process.env.REGION_ID; 5 | const app = `https://testservice-dot-${projectId}.${regionId}.r.appspot.com`; 6 | const assert = require('assert'); 7 | const {v4: uuidv4} = require('uuid'); 8 | const {Firestore} = require('@google-cloud/firestore'); 9 | const fetch = require('node-fetch'); 10 | const {URLSearchParams} = require('url'); 11 | const waitOn = require('wait-on'); 12 | 13 | // eslint-disable-next-line node/no-extraneous-require 14 | const {CloudFunctionsServiceClient} = require('@google-cloud/functions'); 15 | 16 | const opts = { 17 | resources: [app], 18 | }; 19 | 20 | const delay = async (test, addMs) => { 21 | const retries = test.currentRetry(); 22 | await new Promise((r) => setTimeout(r, addMs)); 23 | // No retry on the first failure. 24 | if (retries === 0) return; 25 | // See: https://cloud.google.com/storage/docs/exponential-backoff 26 | const ms = Math.pow(2, retries) + Math.random() * 1000; 27 | return new Promise((done) => { 28 | console.info(`retrying "${test.title}" in ${ms}ms`); 29 | setTimeout(done, ms); 30 | }); 31 | }; 32 | 33 | async function deployService() { 34 | let uniqueID = uuidv4().split('-')[0]; 35 | cp.execSync(`npm install`, {cwd: path.join(__dirname, '../', 'function')}); 36 | try { 37 | cp.execSync( 38 | `gcloud functions deploy translate-${uniqueID} --runtime nodejs10 --allow-unauthenticated --set-env-vars=unique_id=${uniqueID} --trigger-topic translate`, 39 | {cwd: path.join(__dirname, '/testApp')} 40 | ); 41 | } catch (err) { 42 | console.log("Wasn't able to deploy Google Cloud Function"); 43 | } 44 | try { 45 | cp.execSync(`gcloud app deploy app.yaml`, { 46 | cwd: path.join(__dirname, '../', 'server'), 47 | }); 48 | } catch (err) { 49 | console.log("Wasn't able to deploy app to AppEngine"); 50 | } 51 | try { 52 | await waitOn(opts); 53 | } catch (err) { 54 | console.log(err); 55 | } 56 | 57 | return uniqueID; 58 | } 59 | 60 | async function deleteService(uniqueID) { 61 | try { 62 | cp.execSync(`gcloud app services delete testservice`, { 63 | cwd: path.join(__dirname, '/testApp'), 64 | }); 65 | } catch (err) { 66 | console.log('Was not able to delete AppEngine Service'); 67 | } 68 | try { 69 | cp.execSync(`gcloud functions delete translate-${uniqueID}`, { 70 | cwd: path.join(__dirname, '/testApp'), 71 | }); 72 | } catch (err) { 73 | console.log("Wasn't able to delete Google Cloud Functions"); 74 | } 75 | const db = new Firestore({ 76 | project: projectId, 77 | }); 78 | const res = await db.collection('/translations').get(); 79 | res.forEach(async (element) => { 80 | await element.ref.delete(); 81 | }); 82 | console.log('Firebase translation collection deleted'); 83 | } 84 | 85 | async function cleanupServices() { 86 | const client = new CloudFunctionsServiceClient(); 87 | const [functions] = await client.listFunctions({ 88 | parent: `projects/${projectId}/locations/-`, 89 | }); 90 | // We'll delete functions older than 24 hours. 91 | const cutoff = Math.round(new Date().getTime() / 1000) - 24 * 3600; 92 | console.info(`about to delete services using cutoff second at ${cutoff}`); 93 | for (const fn of functions) { 94 | const updateSecond = parseInt(fn.updateTime.seconds, 10); 95 | console.info(`${fn.name} was updated at ${updateSecond}`); 96 | if (updateSecond < cutoff) { 97 | console.info(`it is too old, so deleting ${fn.name}`); 98 | const [operation] = await client.deleteFunction({name: fn.name}); 99 | const [response] = await operation.promise(); 100 | console.log(response); 101 | } 102 | } 103 | } 104 | 105 | describe('behavior of cloud function', function () { 106 | this.timeout(1800000); // 30 mins 107 | 108 | let uniqueID; 109 | before(async () => { 110 | cleanupServices(); 111 | uniqueID = await deployService(); 112 | }); 113 | 114 | after(async () => { 115 | await deleteService(uniqueID); 116 | }); 117 | 118 | it('should get the correct website', async function () { 119 | this.retries(4); 120 | await deployService(); 121 | await delay(this.test, 4000); 122 | 123 | const body = await fetch(`${app}`); 124 | const res = await body.status; 125 | assert.strictEqual(res, 200); 126 | }); 127 | 128 | it('should get the correct response', async function () { 129 | this.retries(6); 130 | this.timeout(1800000); 131 | await delay(this.test, 4000); 132 | const params = new URLSearchParams(); 133 | params.append('lang', 'en'); 134 | params.append('v', 'como estas'); 135 | 136 | let body = await fetch(`${app}/request-translation`, { 137 | method: 'POST', 138 | body: params, 139 | }); 140 | 141 | console.log(await body.text()); 142 | let res = await body.status; 143 | assert.strictEqual(res, 200); 144 | 145 | body = await fetch(`${app}/`); 146 | res = await body.text(); 147 | assert.ok(res.includes('how are you')); 148 | }); 149 | }); 150 | -------------------------------------------------------------------------------- /background/test/testApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "background", 3 | "private": true, 4 | "description": "This directory is a replica of the cloud function in function/", 5 | "main": "translate.js", 6 | "author": "Google LLC", 7 | "license": "Apache-2.0", 8 | "dependencies": { 9 | "@google-cloud/firestore": "^3.0.0", 10 | "@google-cloud/translate": "^6.0.0" 11 | }, 12 | "devDependencies": {} 13 | } 14 | -------------------------------------------------------------------------------- /background/test/testApp/translate.js: -------------------------------------------------------------------------------- 1 | const Firestore = require('@google-cloud/firestore'); 2 | const {Translate} = require('@google-cloud/translate').v2; 3 | 4 | const firestore = new Firestore(); 5 | const translate = new Translate(); 6 | 7 | exports[`translate-${process.env.unique_id}`] = async (pubSubEvent) => { 8 | const {language, original} = JSON.parse( 9 | Buffer.from(pubSubEvent.data, 'base64').toString() 10 | ); 11 | 12 | const [ 13 | translated, 14 | { 15 | data: {translations}, 16 | }, 17 | ] = await translate.translate(original, language); 18 | const originalLanguage = translations[0].detectedSourceLanguage; 19 | console.log( 20 | `Translated ${original} in ${originalLanguage} to ${translated} in ${language}.` 21 | ); 22 | 23 | // Store translation in firestore. 24 | await firestore.collection(`translations`).doc().set({ 25 | language, 26 | original, 27 | translated, 28 | originalLanguage, 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /bookshelf/.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | src/**/doc/* 3 | build/ 4 | docs/ 5 | -------------------------------------------------------------------------------- /bookshelf/.gcloudignore: -------------------------------------------------------------------------------- 1 | # This file specifies files that are *not* uploaded to Google Cloud Platform 2 | # using gcloud. It follows the same syntax as .gitignore, with the addition of 3 | # "#!include" directives (which insert the entries of the given .gitignore-style 4 | # file at that point). 5 | # 6 | # For more information, run: 7 | # $ gcloud topic gcloudignore 8 | # 9 | .gcloudignore 10 | # If you would like to upload your .git directory, .gitignore file or files 11 | # from your .gitignore file, remove the corresponding line 12 | # below: 13 | .git 14 | .gitignore 15 | 16 | # Node.js dependencies: 17 | node_modules/ -------------------------------------------------------------------------------- /bookshelf/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /bookshelf/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official NodeJS image. 2 | # https://hub.docker.com/_/node 3 | FROM node:16-slim 4 | 5 | # Copy local code to the container image. 6 | ENV APP_HOME /app 7 | WORKDIR $APP_HOME 8 | COPY . ./ 9 | 10 | # Install production dependencies. 11 | RUN npm install --only=production 12 | 13 | # Run the web service on container startup. 14 | ENTRYPOINT [ "npm", "start" ] -------------------------------------------------------------------------------- /bookshelf/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Node.js on Google Cloud Platform 2 | 3 | This directory contains the code for deploying a basic Node.js app to Google Cloud Platform. Follow the tutorial to use this sample. 4 | 5 | * [Getting started with Node.js](https://cloud.google.com/nodejs/getting-started) -------------------------------------------------------------------------------- /bookshelf/app.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017, Google, Inc. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | 'use strict'; 15 | 16 | const express = require('express'); 17 | 18 | const app = express(); 19 | 20 | app.set('views', require('path').join(__dirname, 'views')); 21 | app.set('view engine', 'pug'); 22 | 23 | // Books 24 | app.use('/books', require('./books/crud')); 25 | app.use('/api/books', require('./books/api')); 26 | 27 | // Redirect root to /books 28 | app.get('/', (req, res) => { 29 | res.redirect('/books'); 30 | }); 31 | 32 | app.get('/errors', () => { 33 | throw new Error('Test exception'); 34 | }); 35 | 36 | app.get('/logs', (req, res) => { 37 | console.log('Hey, you triggered a custom log entry. Good job!'); 38 | res.sendStatus(200); 39 | }); 40 | 41 | // Start the server 42 | const port = process.env.PORT || 8080; 43 | app.listen(port, () => { 44 | console.log(`App listening on port ${port}`); 45 | }); 46 | 47 | module.exports = app; 48 | -------------------------------------------------------------------------------- /bookshelf/app.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2017, Google, Inc. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # 14 | # [START bookshelf_app_yml] 15 | runtime: nodejs10 16 | 17 | instance_class: F2 18 | 19 | # [END bookshelf_app_yml] 20 | -------------------------------------------------------------------------------- /bookshelf/books/api.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017, Google, Inc. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | 'use strict'; 15 | 16 | const express = require('express'); 17 | const bodyParser = require('body-parser'); 18 | const db = require('./firestore'); 19 | 20 | const router = express.Router(); 21 | 22 | // Automatically parse request body as JSON 23 | router.use(bodyParser.json()); 24 | 25 | /** 26 | * GET /api/books 27 | * 28 | * Retrieve a page of books (up to ten at a time). 29 | */ 30 | router.get('/', async (req, res) => { 31 | const {books, nextPageToken} = await db.list(10, req.query.pageToken); 32 | res.json({ 33 | items: books, 34 | nextPageToken, 35 | }); 36 | }); 37 | 38 | /** 39 | * POST /api/books 40 | * 41 | * Create a new book. 42 | */ 43 | router.post('/', async (req, res) => { 44 | const book = await db.create(req.body); 45 | res.json(book); 46 | }); 47 | 48 | /** 49 | * GET /api/books/:id 50 | * 51 | * Retrieve a book. 52 | */ 53 | router.get('/:book', async (req, res) => { 54 | const book = await db.read(req.params.book); 55 | res.json(book); 56 | }); 57 | 58 | /** 59 | * PUT /api/books/:id 60 | * 61 | * Update a book. 62 | */ 63 | router.put('/:book', async (req, res) => { 64 | const book = await db.update(req.params.book, req.body); 65 | res.json(book); 66 | }); 67 | 68 | /** 69 | * DELETE /api/books/:id 70 | * 71 | * Delete a book. 72 | */ 73 | router.delete('/:book', async (req, res) => { 74 | await db.delete(req.params.book); 75 | res.status(200).send('OK'); 76 | }); 77 | 78 | module.exports = router; 79 | -------------------------------------------------------------------------------- /bookshelf/books/crud.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017, Google, Inc. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | 'use strict'; 15 | 16 | const express = require('express'); 17 | const bodyParser = require('body-parser'); 18 | const images = require('../lib/images'); 19 | const db = require('./firestore'); 20 | 21 | const router = express.Router(); 22 | 23 | // Automatically parse request body as form data 24 | router.use(bodyParser.urlencoded({extended: false})); 25 | 26 | // Set Content-Type for all responses for these routes 27 | router.use((req, res, next) => { 28 | res.set('Content-Type', 'text/html'); 29 | next(); 30 | }); 31 | 32 | /** 33 | * GET /books 34 | * 35 | * Display a page of books (up to ten at a time). 36 | */ 37 | router.get('/', async (req, res) => { 38 | let {books, nextPageToken} = await db.list(10, req.query.pageToken); 39 | res.render('books/list.pug', { 40 | books, 41 | nextPageToken, 42 | }); 43 | }); 44 | 45 | /** 46 | * GET /books/add 47 | * 48 | * Display a form for creating a book. 49 | */ 50 | router.get('/add', (req, res) => { 51 | res.render('books/form.pug', { 52 | book: {}, 53 | action: 'Add', 54 | }); 55 | }); 56 | 57 | /** 58 | * POST /books/add 59 | * 60 | * Create a book. 61 | */ 62 | router.post( 63 | '/add', 64 | images.multer.single('image'), 65 | images.sendUploadToGCS, 66 | async (req, res) => { 67 | let data = req.body; 68 | 69 | // Was an image uploaded? If so, we'll use its public URL 70 | // in cloud storage. 71 | if (req.file && req.file.cloudStoragePublicUrl) { 72 | data.imageUrl = req.file.cloudStoragePublicUrl; 73 | } 74 | 75 | // Save the data to the database. 76 | const savedData = await db.create(data); 77 | res.redirect(`${req.baseUrl}/${savedData.id}`); 78 | } 79 | ); 80 | 81 | /** 82 | * GET /books/:id/edit 83 | * 84 | * Display a book for editing. 85 | */ 86 | router.get('/:book/edit', async (req, res) => { 87 | const book = await db.read(req.params.book); 88 | res.render('books/form.pug', { 89 | book, 90 | action: 'Edit', 91 | }); 92 | }); 93 | 94 | /** 95 | * POST /books/:id/edit 96 | * 97 | * Update a book. 98 | */ 99 | router.post( 100 | '/:book/edit', 101 | images.multer.single('image'), 102 | images.sendUploadToGCS, 103 | async (req, res) => { 104 | let data = req.body; 105 | 106 | // Was an image uploaded? If so, we'll use its public URL 107 | // in cloud storage. 108 | if (req.file && req.file.cloudStoragePublicUrl) { 109 | req.body.imageUrl = req.file.cloudStoragePublicUrl; 110 | } 111 | 112 | const savedData = await db.update(req.params.book, data); 113 | res.redirect(`${req.baseUrl}/${savedData.id}`); 114 | } 115 | ); 116 | 117 | /** 118 | * GET /books/:id 119 | * 120 | * Display a book. 121 | */ 122 | router.get('/:book', async (req, res) => { 123 | const book = await db.read(req.params.book); 124 | res.render('books/view.pug', { 125 | book, 126 | }); 127 | }); 128 | 129 | /** 130 | * GET /books/:id/delete 131 | * 132 | * Delete a book. 133 | */ 134 | router.get('/:book/delete', async (req, res) => { 135 | await db.delete(req.params.book); 136 | res.redirect(req.baseUrl); 137 | }); 138 | 139 | module.exports = router; 140 | -------------------------------------------------------------------------------- /bookshelf/books/firestore.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Google, Inc. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | 'use strict'; 15 | 16 | // [START bookshelf_firestore_client] 17 | const {Firestore} = require('@google-cloud/firestore'); 18 | 19 | const db = new Firestore(); 20 | const collection = 'Book'; 21 | 22 | // [END bookshelf_firestore_client] 23 | 24 | // Lists all books in the database sorted alphabetically by title. 25 | async function list(limit, token) { 26 | const snapshot = await db 27 | .collection(collection) 28 | .orderBy('title') 29 | .startAfter(token || '') 30 | .limit(limit) 31 | .get(); 32 | 33 | if (snapshot.empty) { 34 | return { 35 | books: [], 36 | nextPageToken: false, 37 | }; 38 | } 39 | const books = []; 40 | snapshot.forEach((doc) => { 41 | let book = doc.data(); 42 | book.id = doc.id; 43 | books.push(book); 44 | }); 45 | const q = await snapshot.query.offset(limit).get(); 46 | 47 | return { 48 | books, 49 | nextPageToken: q.empty ? false : books[books.length - 1].title, 50 | }; 51 | } 52 | 53 | // Creates a new book or updates an existing book with new data. 54 | async function update(id, data) { 55 | let ref; 56 | if (id === null) { 57 | ref = db.collection(collection).doc(); 58 | } else { 59 | ref = db.collection(collection).doc(id); 60 | } 61 | 62 | data.id = ref.id; 63 | data = {...data}; 64 | await ref.set(data); 65 | return data; 66 | } 67 | 68 | async function create(data) { 69 | return await update(null, data); 70 | } 71 | 72 | // [START bookshelf_firestore_client_get_book] 73 | async function read(id) { 74 | const doc = await db.collection(collection).doc(id).get(); 75 | 76 | if (!doc.exists) { 77 | throw new Error('No such document!'); 78 | } 79 | return doc.data(); 80 | } 81 | // [END bookshelf_firestore_client_get_book] 82 | 83 | async function _delete(id) { 84 | await db.collection(collection).doc(id).delete(); 85 | } 86 | 87 | module.exports = { 88 | create, 89 | read, 90 | update, 91 | delete: _delete, 92 | list, 93 | }; 94 | -------------------------------------------------------------------------------- /bookshelf/lib/images.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Google, Inc. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | 'use strict'; 15 | 16 | const GOOGLE_CLOUD_PROJECT = process.env['GOOGLE_CLOUD_PROJECT']; 17 | const CLOUD_BUCKET = GOOGLE_CLOUD_PROJECT + '_bucket'; 18 | 19 | // [START bookshelf_cloud_storage_client] 20 | const {Storage} = require('@google-cloud/storage'); 21 | 22 | const storage = new Storage(); 23 | const bucket = storage.bucket(CLOUD_BUCKET); 24 | // [END bookshelf_cloud_storage_client] 25 | 26 | // Returns the public, anonymously accessible URL to a given Cloud Storage 27 | // object. 28 | // The object's ACL has to be set to public read. 29 | // [START public_url] 30 | function getPublicUrl(filename) { 31 | return `https://storage.googleapis.com/${CLOUD_BUCKET}/${filename}`; 32 | } 33 | // [END public_url] 34 | 35 | // Express middleware that will automatically pass uploads to Cloud Storage. 36 | // req.file is processed and will have two new properties: 37 | // * ``cloudStorageObject`` the object name in cloud storage. 38 | // * ``cloudStoragePublicUrl`` the public url to the object. 39 | // [START process] 40 | function sendUploadToGCS(req, res, next) { 41 | if (!req.file) { 42 | return next(); 43 | } 44 | 45 | const gcsname = Date.now() + req.file.originalname; 46 | const file = bucket.file(gcsname); 47 | 48 | const stream = file.createWriteStream({ 49 | metadata: { 50 | contentType: req.file.mimetype, 51 | }, 52 | resumable: false, 53 | }); 54 | 55 | stream.on('error', (err) => { 56 | req.file.cloudStorageError = err; 57 | next(err); 58 | }); 59 | 60 | stream.on('finish', async () => { 61 | req.file.cloudStorageObject = gcsname; 62 | await file.makePublic(); 63 | req.file.cloudStoragePublicUrl = getPublicUrl(gcsname); 64 | next(); 65 | }); 66 | 67 | stream.end(req.file.buffer); 68 | } 69 | // [END process] 70 | 71 | // Multer handles parsing multipart/form-data requests. 72 | // This instance is configured to store images in memory. 73 | // This makes it straightforward to upload to Cloud Storage. 74 | // [START multer] 75 | const Multer = require('multer'); 76 | const multer = Multer({ 77 | storage: Multer.MemoryStorage, 78 | limits: { 79 | fileSize: 5 * 1024 * 1024, // no larger than 5mb 80 | }, 81 | }); 82 | // [END multer] 83 | 84 | module.exports = { 85 | getPublicUrl, 86 | sendUploadToGCS, 87 | multer, 88 | }; 89 | -------------------------------------------------------------------------------- /bookshelf/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-getting-started", 3 | "description": "End to end sample for running Node.js applications on Google Cloud Platform", 4 | "license": "Apache-2.0", 5 | "author": "Google LLC", 6 | "engines": { 7 | "node": ">=12" 8 | }, 9 | "repository": "https://github.com/GoogleCloudPlatform/nodejs-getting-started", 10 | "main": "app.js", 11 | "private": true, 12 | "scripts": { 13 | "start": "node app.js", 14 | "test": "mocha --timeout=8000 --exit **/*.test.js" 15 | }, 16 | "dependencies": { 17 | "@google-cloud/firestore": "^5.0.0", 18 | "@google-cloud/storage": "^6.0.0", 19 | "body-parser": "^1.18.3", 20 | "express": "^4.16.4", 21 | "multer": "^1.4.1", 22 | "pug": "^3.0.0" 23 | }, 24 | "devDependencies": { 25 | "mocha": "^9.0.0", 26 | "supertest": "^6.0.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /bookshelf/test/app.test.js: -------------------------------------------------------------------------------- 1 | const app = require('../app'); 2 | 3 | const request = require('supertest'); 4 | 5 | describe('Requests have valid status codes', () => { 6 | it('should get 302', (done) => { 7 | request(app).get('/').expect(302, done); 8 | }), 9 | it('should get books', (done) => { 10 | request(app).get('/books').expect(200, done); 11 | }); 12 | it('should get books/add form', (done) => { 13 | request(app).get('/books/add').expect(200, done); 14 | }); 15 | }); 16 | 17 | describe('Should have logs and errors endpoints as described in docs for Stackdriver', () => { 18 | it('should have logs endpoints', (done) => { 19 | request(app).get('/logs').expect(200, done); 20 | }), 21 | it('should have errors endpoint', (done) => { 22 | request(app).get('/errors').expect(500, done); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /bookshelf/views/base.pug: -------------------------------------------------------------------------------- 1 | //- Copyright 2017, Google, Inc. 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | doctype html 15 | html(lang="en") 16 | head 17 | title Bookshelf - Node.js on Google Cloud Platform 18 | meta(charset='utf-8') 19 | meta(name="viewport", content="width=device-width, initial-scale=1") 20 | link(rel="stylesheet", href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css") 21 | body 22 | .navbar.navbar-default 23 | .container 24 | .navbar-header 25 | .navbar-brand Bookshelf 26 | 27 | ul.nav.navbar-nav 28 | li 29 | a(href="/books") Books 30 | 31 | .container 32 | block content 33 | -------------------------------------------------------------------------------- /bookshelf/views/books/form.pug: -------------------------------------------------------------------------------- 1 | //- Copyright 2017, Google, Inc. 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | extends ../base.pug 15 | 16 | block content 17 | h3 #{action} book 18 | form(method="POST", enctype="multipart/form-data") 19 | .form-group 20 | label(for="title") Title 21 | input.form-control(type="text", name="title", id="title", value=book.title) 22 | .form-group 23 | label(for="author") Author 24 | input.form-control(type="text", name="author", id="author", value=book.author) 25 | .form-group 26 | label(for="publishedDate") Date Published 27 | input.form-control(type="text", name="publishedDate", id="publishedDate", value=book.publishedDate) 28 | .form-group 29 | label(for="description") Description 30 | input.form-control(type="text", name="description", id="description", value=book.description) 31 | .form-group 32 | label(for="image") Cover Image 33 | input.form-control(type="file", name="image", id="image") 34 | .form-group.hidden 35 | label(for="imageUrl") Cover Image URL 36 | input.form-control(type="text", name="imageUrl", id="imageUrl", value=book.imageUrl) 37 | button.btn.btn-success(type="submit") Save 38 | -------------------------------------------------------------------------------- /bookshelf/views/books/list.pug: -------------------------------------------------------------------------------- 1 | //- Copyright 2017, Google, Inc. 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | extends ../base.pug 15 | 16 | block content 17 | h3 Books 18 | 19 | a(href="/books/add", class='btn btn-success btn-sm') 20 | i.glyphicon.glyphicon-plus 21 | span Add book 22 | 23 | each book in books 24 | .media 25 | a(href=`/books/${book.id}`) 26 | .media-left 27 | img(src=book.imageUrl || "http://placekitten.com/g/128/192") 28 | .media-body 29 | h4= book.title 30 | p= book.author 31 | 32 | if !books.length 33 | p No books found. 34 | 35 | if nextPageToken 36 | nav 37 | ul.pager 38 | li 39 | a(href=`?pageToken=${encodeURIComponent(nextPageToken)}`) More 40 | -------------------------------------------------------------------------------- /bookshelf/views/books/view.pug: -------------------------------------------------------------------------------- 1 | //- Copyright 2017, Google, Inc. 2 | Licensed under the Apache License, Version 2.0 (the "License"); 3 | you may not use this file except in compliance with the License. 4 | You may obtain a copy of the License at 5 | 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | 8 | Unless required by applicable law or agreed to in writing, software 9 | distributed under the License is distributed on an "AS IS" BASIS, 10 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | See the License for the specific language governing permissions and 12 | limitations under the License. 13 | 14 | extends ../base.pug 15 | 16 | block content 17 | h3 Book 18 | small 19 | 20 | .btn-group 21 | a(href=`/books/${book.id}/edit`, class='btn btn-primary btn-sm') 22 | i.glyphicon.glyphicon-edit 23 | span Edit book 24 | a(href=`/books/${book.id}/delete`, class='btn btn-danger btn-sm') 25 | i.glyphicon.glyphicon-trash 26 | span Delete book 27 | 28 | .media 29 | .media-left 30 | img(src=book.imageUrl || "http://placekitten.com/g/128/192") 31 | .media-body 32 | h4= book.title 33 | |   34 | small= book.publishedDate 35 | h5 By #{book.author||'unknown'} 36 | p= book.description 37 | -------------------------------------------------------------------------------- /ci/.mocharc.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | const config = {}; 15 | 16 | if (process.env.MOCHA_THROW_DEPRECATION === 'false') { 17 | delete config['throw-deprecation']; 18 | } 19 | if (process.env.MOCHA_REPORTER) { 20 | config.reporter = process.env.MOCHA_REPORTER; 21 | } 22 | 23 | const reporterOptions = []; 24 | if (process.env.MOCHA_REPORTER_OUTPUT) { 25 | reporterOptions.push(`output=${process.env.MOCHA_REPORTER_OUTPUT}`); 26 | } 27 | if (process.env.MOCHA_REPORTER_SUITENAME) { 28 | reporterOptions.push(`suiteName=${process.env.MOCHA_REPORTER_SUITENAME}`); 29 | } 30 | config['reporter-option'] = reporterOptions; 31 | 32 | module.exports = config 33 | -------------------------------------------------------------------------------- /ci/cloudbuild/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ARG NODE_VERSION=14 16 | 17 | FROM golang:1.19.4-alpine3.17 as build 18 | 19 | RUN apk add --no-cache curl tar python3 git 20 | 21 | # Install gcloud 22 | RUN curl https://dl.google.com/dl/cloudsdk/release/google-cloud-sdk.tar.gz > /tmp/google-cloud-sdk.tar.gz 23 | 24 | RUN mkdir -p /usr/local/gcloud \ 25 | && tar -C /usr/local/gcloud -xvf /tmp/google-cloud-sdk.tar.gz \ 26 | && /usr/local/gcloud/google-cloud-sdk/install.sh 27 | 28 | # Download flakybot release 29 | RUN curl https://github.com/googleapis/repo-automation-bots/releases/download/flakybot-1.1.0/flakybot \ 30 | -o /bin/flakybot -s -L \ 31 | && chmod +x /bin/flakybot 32 | 33 | # Build btlr 34 | RUN git clone https://github.com/googleapis/btlr.git \ 35 | && cd btlr \ 36 | && git checkout 9d264287b11b3bb556a3b7204c5b61bd1da4d96c \ 37 | && go build \ 38 | && cp btlr /bin/btlr \ 39 | && chmod 0755 /bin/btlr 40 | 41 | FROM node:${NODE_VERSION}-alpine 42 | 43 | COPY --from=build /bin/flakybot /bin/flakybot 44 | COPY --from=build /bin/btlr /bin/btlr 45 | 46 | # Hack for not found error with flakybot 47 | RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2 48 | 49 | COPY --from=build /usr/local/gcloud /usr/local/gcloud 50 | RUN apk add --no-cache git bash python3 51 | 52 | ENV PATH $PATH:/usr/local/gcloud/google-cloud-sdk/bin 53 | -------------------------------------------------------------------------------- /ci/cloudbuild/cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | options: 16 | dynamic_substitutions: true 17 | 18 | substitutions: 19 | _NODE_VERSION: "14" 20 | _BUILD_TYPE: "presubmit" 21 | _TRIGGER_SOURCE: '${_PR_NUMBER:-main}' 22 | 23 | logsBucket: 'gs://${_LOGS_BUCKET}/logs/nodejs-getting-started/${_TRIGGER_SOURCE}/${COMMIT_SHA}/${TRIGGER_NAME}' 24 | timeout: 7200s 25 | 26 | steps: 27 | - name: 'gcr.io/kaniko-project/executor:v1.10.0' 28 | args: [ 29 | '--log-format=text', 30 | '--context=dir:///workspace/testing', 31 | '--build-arg=NODE_VERSION=${_NODE_VERSION}', 32 | '--dockerfile=ci/cloudbuild/Dockerfile', 33 | '--cache=true', 34 | '--destination=gcr.io/${PROJECT_ID}/${_IMAGE_PREFIX}${_NODE_VERSION}', 35 | '--push-retry=3', 36 | '--image-fs-extract-retry=3' 37 | ] 38 | 39 | - name: gcr.io/${PROJECT_ID}/${_IMAGE_PREFIX}${_NODE_VERSION} 40 | id: "test-driver" 41 | timeout: 7200s 42 | entrypoint: "bash" 43 | env: 44 | - 'BUILD_TYPE=${_BUILD_TYPE}' 45 | - 'GCLOUD_PROJECT=${PROJECT_ID}' 46 | - 'PROJECT_ID=${PROJECT_ID}' 47 | - 'COMMIT_SHA=${COMMIT_SHA}' 48 | - 'BUILD_ID=${BUILD_ID}' 49 | - 'REPO_OWNER=${_REPO_OWNER}' 50 | - 'REPO_NAME=${REPO_NAME}' 51 | args: [ 52 | "ci/cloudbuild/run_test.sh", 53 | ] 54 | -------------------------------------------------------------------------------- /ci/cloudbuild/create_build_triggers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2022 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # A script for creating Cloud Build build triggers. 18 | 19 | # `-e` enables the script to automatically fail when a command fails 20 | # `-o pipefail` sets the exit code to the rightmost comment to exit 21 | # with a non-zero 22 | set -eo pipefail 23 | 24 | REPO_OWNER="GoogleCloudPlatform" 25 | REPO_NAME="nodejs-getting-started" 26 | 27 | NODE_VERSIONS=( 28 | "14" 29 | ) 30 | 31 | for NODE_VERSION in ${NODE_VERSIONS[@]}; do 32 | # Creating presubmit build 33 | gcloud beta builds triggers create github \ 34 | --name=gcb-presubmit-node${NODE_VERSION} \ 35 | --service-account="projects/firestore-nodejs-getting-start/serviceAccounts/firestore-nodejs-getting-start@firestore-nodejs-getting-start.iam.gserviceaccount.com" \ 36 | --description="Presubmit build with node ${NODE_VERSION}" \ 37 | --repo-name="${REPO_NAME}" \ 38 | --repo-owner="${REPO_OWNER}" \ 39 | --pull-request-pattern="^main$" \ 40 | --build-config="ci/cloudbuild/cloudbuild.yaml" \ 41 | --substitutions="_BUILD_TYPE=presubmit,_LOGS_BUCKET=nodejs-samples-publiclogs,_NODE_VERSION=${NODE_VERSION},_IMAGE_PREFIX=nodejs-getting-started-test-node,_REPO_OWNER=GoogleCloudPlatform" \ 42 | --comment-control="COMMENTS_ENABLED_FOR_EXTERNAL_CONTRIBUTORS_ONLY" \ 43 | --include-logs-with-status 44 | 45 | 46 | # Creating continuous build 47 | gcloud beta builds triggers create github \ 48 | --name=gcb-continuous-node${NODE_VERSION} \ 49 | --service-account="projects/firestore-nodejs-getting-start/serviceAccounts/firestore-nodejs-getting-start@firestore-nodejs-getting-start.iam.gserviceaccount.com" \ 50 | --description="Continuous build with node ${NODE_VERSION}" \ 51 | --repo-name="${REPO_NAME}" \ 52 | --repo-owner="${REPO_OWNER}" \ 53 | --branch-pattern="^main$" \ 54 | --build-config="ci/cloudbuild/cloudbuild.yaml" \ 55 | --substitutions="_BUILD_TYPE=continuous,_LOGS_BUCKET=nodejs-samples-publiclogs,_NODE_VERSION=${NODE_VERSION},_IMAGE_PREFIX=nodejs-getting-started-test-node,_REPO_OWNER=GoogleCloudPlatform" 56 | 57 | done 58 | -------------------------------------------------------------------------------- /ci/cloudbuild/export/gcb-continuous-node14.yaml: -------------------------------------------------------------------------------- 1 | createTime: '2022-05-04T03:55:52.095944708Z' 2 | description: Continuous build with node 14 3 | filename: ci/cloudbuild/cloudbuild.yaml 4 | github: 5 | name: nodejs-getting-started 6 | owner: GoogleCloudPlatform 7 | push: 8 | branch: ^main$ 9 | id: d6a69575-6f93-434c-9b0b-6c5ab9751e81 10 | name: gcb-continuous-node14 11 | serviceAccount: projects/firestore-nodejs-getting-start/serviceAccounts/firestore-nodejs-getting-start@firestore-nodejs-getting-start.iam.gserviceaccount.com 12 | substitutions: 13 | _BUILD_TYPE: continuous 14 | _IMAGE_PREFIX: nodejs-getting-started-test-node 15 | _LOGS_BUCKET: nodejs-samples-publiclogs 16 | _NODE_VERSION: '14' 17 | _REPO_OWNER: GoogleCloudPlatform 18 | -------------------------------------------------------------------------------- /ci/cloudbuild/export/gcb-nightly-node14.yaml: -------------------------------------------------------------------------------- 1 | createTime: '2022-05-04T03:57:46.290349757Z' 2 | description: Nightly build with node 14 3 | gitFileSource: 4 | path: ci/cloudbuild/cloudbuild.yaml 5 | repoType: GITHUB 6 | revision: refs/heads/main 7 | uri: https://github.com/GoogleCloudPlatform/nodejs-getting-started 8 | id: aaa1f78a-1227-48d8-b09e-7dc9083ba638 9 | name: gcb-nightly-node14 10 | serviceAccount: projects/firestore-nodejs-getting-start/serviceAccounts/firestore-nodejs-getting-start@firestore-nodejs-getting-start.iam.gserviceaccount.com 11 | sourceToBuild: 12 | ref: refs/heads/main 13 | repoType: GITHUB 14 | uri: https://github.com/GoogleCloudPlatform/nodejs-getting-started 15 | substitutions: 16 | _BUILD_TYPE: nightly 17 | _IMAGE_PREFIX: nodejs-getting-started-test-node 18 | _LOGS_BUCKET: nodejs-samples-publiclogs 19 | _NODE_VERSION: '14' 20 | _REPO_OWNER: GoogleCloudPlatform 21 | -------------------------------------------------------------------------------- /ci/cloudbuild/export/gcb-presubmit-node14.yaml: -------------------------------------------------------------------------------- 1 | createTime: '2022-05-04T03:55:50.388159673Z' 2 | description: Presubmit build with node 14 3 | filename: ci/cloudbuild/cloudbuild.yaml 4 | github: 5 | name: nodejs-getting-started 6 | owner: GoogleCloudPlatform 7 | pullRequest: 8 | branch: ^main$ 9 | commentControl: COMMENTS_ENABLED_FOR_EXTERNAL_CONTRIBUTORS_ONLY 10 | id: f2ead741-9858-460c-8f5c-23cc77714c03 11 | includeBuildLogs: INCLUDE_BUILD_LOGS_WITH_STATUS 12 | name: gcb-presubmit-node14 13 | serviceAccount: projects/firestore-nodejs-getting-start/serviceAccounts/firestore-nodejs-getting-start@firestore-nodejs-getting-start.iam.gserviceaccount.com 14 | substitutions: 15 | _BUILD_TYPE: presubmit 16 | _IMAGE_PREFIX: nodejs-getting-started-test-node 17 | _LOGS_BUCKET: nodejs-samples-publiclogs 18 | _NODE_VERSION: '14' 19 | _REPO_OWNER: GoogleCloudPlatform 20 | -------------------------------------------------------------------------------- /ci/cloudbuild/export_triggers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2022 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # A script for exporting Cloud Build build triggers. 18 | 19 | # `-e` enables the script to automatically fail when a command fails 20 | # `-o pipefail` sets the exit code to the rightmost comment to exit 21 | # with a non-zero 22 | set -eo pipefail 23 | 24 | NODE_VERSIONS=( 25 | "14" 26 | ) 27 | 28 | PROGRAM_PATH="$(realpath "$0")" 29 | PROGRAM_DIR="$(dirname "${PROGRAM_PATH}")" 30 | 31 | # Always move to the project root. 32 | echo "change directory to the project root" 33 | cd ${PROGRAM_DIR}/../.. 34 | pwd 35 | 36 | echo "exporting Cloud Build triggers" 37 | 38 | for NODE_VERSION in ${NODE_VERSIONS[@]}; do 39 | echo "exporting presubmit build for node${NODE_VERSION}" 40 | gcloud beta builds triggers export "gcb-presubmit-node${NODE_VERSION}" --destination "ci/cloudbuild/export/gcb-presubmit-node${NODE_VERSION}.yaml" 41 | echo "exporting continuous build for node${NODE_VERSION}" 42 | gcloud beta builds triggers export "gcb-continuous-node${NODE_VERSION}" --destination "ci/cloudbuild/export/gcb-continuous-node${NODE_VERSION}.yaml" 43 | echo "exporting nightly build for node${NODE_VERSION}" 44 | gcloud beta builds triggers export "gcb-nightly-node${NODE_VERSION}" --destination "ci/cloudbuild/export/gcb-nightly-node${NODE_VERSION}.yaml" 45 | done 46 | -------------------------------------------------------------------------------- /ci/cloudbuild/import_triggers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2022 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # A script for importing Cloud Build build triggers. 18 | 19 | # `-e` enables the script to automatically fail when a command fails 20 | # `-o pipefail` sets the exit code to the rightmost comment to exit 21 | # with a non-zero 22 | set -eo pipefail 23 | 24 | NODE_VERSIONS=( 25 | "14" 26 | ) 27 | 28 | PROGRAM_PATH="$(realpath "$0")" 29 | PROGRAM_DIR="$(dirname "${PROGRAM_PATH}")" 30 | 31 | # Always move to the project root. 32 | echo "change directory to the project root" 33 | cd ${PROGRAM_DIR}/../.. 34 | pwd 35 | 36 | echo "importing Cloud Build triggers" 37 | 38 | for NODE_VERSION in ${NODE_VERSIONS[@]}; do 39 | echo "importing presubmit build for node${NODE_VERSION}" 40 | gcloud beta builds triggers import --source "ci/cloudbuild/export/gcb-presubmit-node${NODE_VERSION}.yaml" 41 | echo "importing continuous build for node${NODE_VERSION}" 42 | gcloud beta builds triggers import --source "ci/cloudbuild/export/gcb-continuous-node${NODE_VERSION}.yaml" 43 | echo "importing nightly build for node${NODE_VERSION}" 44 | gcloud beta builds triggers import --source "ci/cloudbuild/export/gcb-nightly-node${NODE_VERSION}.yaml" 45 | done 46 | -------------------------------------------------------------------------------- /ci/cloudbuild/run_single_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2022 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -e 18 | 19 | export REGION_ID='uc' 20 | 21 | d=$(pwd) 22 | PROJECT=$(basename ${d}) 23 | 24 | if [ ${BUILD_TYPE} != "presubmit" ]; then 25 | # Activate mocha config 26 | export MOCHA_REPORTER_OUTPUT=${PROJECT}_sponge_log.xml 27 | export MOCHA_REPORTER_SUITENAME=${PROJECT} 28 | export MOCHA_REPORTER=xunit 29 | cp ${PROJECT_ROOT}/ci/.mocharc.js . 30 | fi 31 | 32 | # Install dependencies 33 | npm install 34 | 35 | set +e 36 | npm test 37 | retval=$? 38 | set -e 39 | 40 | # Run flakybot for non-presubmit builds 41 | if [ ${BUILD_TYPE} != "presubmit" ]; then 42 | echo "Contents in ${PROJECT}_sponge_log.xml:" 43 | cat ${PROJECT}_sponge_log.xml 44 | 45 | echo "Calling flakybot --repo ${REPO_OWNER}/${REPO_NAME} --commit_hash ${COMMIT_SHA} --build_url https://console.cloud.google.com/cloud-build/builds;region=global/${BUILD_ID}?project=${PROJECT_ID}" 46 | flakybot \ 47 | --repo "${REPO_OWNER}/${REPO_NAME}" \ 48 | --commit_hash "${COMMIT_SHA}" \ 49 | --build_url \ 50 | "https://console.cloud.google.com/cloud-build/builds;region=global/${BUILD_ID}?project=${PROJECT_ID}" 51 | fi 52 | 53 | exit ${retval} 54 | -------------------------------------------------------------------------------- /ci/cloudbuild/run_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright 2022 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | 18 | # `-e` enables the script to automatically fail when a command fails 19 | # `-o pipefail` sets the exit code to the rightmost comment to exit 20 | # with a non-zero 21 | set -eo pipefail 22 | 23 | # In cloudbuild, the current directory is the project root. 24 | export PROJECT_ROOT=$(pwd) 25 | 26 | # A script file for running the test in a sub project. 27 | test_script="${PROJECT_ROOT}/ci/cloudbuild/run_single_test.sh" 28 | 29 | # btlr binary 30 | btlr_bin="/bin/btlr" 31 | 32 | if [ ${BUILD_TYPE} == "presubmit" ]; then 33 | 34 | # First run lint and exit early upon failure 35 | npm install 36 | npm run lint 37 | 38 | # For presubmit build, we want to know the difference from the 39 | # common commit in origin/main. 40 | GIT_DIFF_ARG="origin/main..." 41 | 42 | # Then fetch enough history for finding the common commit. 43 | git fetch origin main --deepen=200 44 | 45 | elif [ ${BUILD_TYPE} == "continuous" ]; then 46 | # For continuous build, we want to know the difference in the last 47 | # commit. This assumes we use squash commit when merging PRs. 48 | GIT_DIFF_ARG="HEAD~.." 49 | 50 | # Then fetch one last commit for getting the diff. 51 | git fetch origin main --deepen=1 52 | 53 | else 54 | # Run everything. 55 | GIT_DIFF_ARG="" 56 | fi 57 | 58 | # Then detect changes in the test scripts and Dockerfile. 59 | 60 | set +e 61 | git diff --quiet ${GIT_DIFF_ARG} ci/cloudbuild 62 | changed=$? 63 | set -e 64 | if [[ "${changed}" -eq 0 ]]; then 65 | echo "no change detected in ci/cloudbuild" 66 | else 67 | echo "change detected in ci/cloudbuild, we should test everything" 68 | GIT_DIFF_ARG="" 69 | fi 70 | 71 | # Now we have a fixed list, but we can change it to autodetect if 72 | # necessary. 73 | 74 | subdirs=( 75 | authenticating-users 76 | background 77 | bookshelf 78 | gce 79 | sessions 80 | ) 81 | 82 | dirs_to_test=() 83 | 84 | for d in ${subdirs[@]}; do 85 | if [ -n "${GIT_DIFF_ARG}" ]; then 86 | echo "checking changes with 'git diff --quiet ${GIT_DIFF_ARG} ${d}'" 87 | set +e 88 | git diff --quiet ${GIT_DIFF_ARG} ${d} 89 | changed=$? 90 | set -e 91 | if [[ "${changed}" -eq 0 ]]; then 92 | echo "no change detected in ${d}, skipping" 93 | else 94 | echo "change detected in ${d}" 95 | dirs_to_test+=(${d}) 96 | fi 97 | else 98 | # If GIT_DIFF_ARG is empty, run all the tests. 99 | dirs_to_test+=(${d}) 100 | fi 101 | done 102 | 103 | if [ ${#dirs_to_test[@]} -gt 0 ]; then 104 | ${btlr_bin} run ${dirs_to_test[@]} -- ${test_script} 105 | else 106 | echo "Nothing changed in the samples" 107 | fi 108 | -------------------------------------------------------------------------------- /gce/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config.json 3 | *.log 4 | -------------------------------------------------------------------------------- /gce/README.md: -------------------------------------------------------------------------------- 1 | # Hello World for Node.js on Google Compute Engine 2 | 3 | This folder contains the sample code for the [Deploying to Google Compute Engine][tutorial-gce] 4 | tutorial. Please refer to the tutorial for instructions on configuring, running, 5 | and deploying this sample. 6 | 7 | [tutorial-gce]: https://cloud.google.com/nodejs/tutorials/getting-started-on-compute-engine -------------------------------------------------------------------------------- /gce/app.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | const express = require('express'); 18 | 19 | const app = express(); 20 | 21 | app.get('/', (req, res) => { 22 | res.status(200).send('Hello, world!').end(); 23 | }); 24 | 25 | // Start the server 26 | const PORT = process.env.PORT || 8080; 27 | app.listen(PORT, () => { 28 | console.log(`App listening on port ${PORT}`); 29 | console.log('Press Ctrl+C to quit.'); 30 | }); 31 | 32 | module.exports = app; 33 | -------------------------------------------------------------------------------- /gce/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gce-hello-world", 3 | "description": "Simple Hello World Node.js sample for Google Compute Engine.", 4 | "version": "0.0.1", 5 | "private": true, 6 | "license": "Apache-2.0", 7 | "author": "Google Inc.", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/GoogleCloudPlatform/nodejs-getting-started.git" 11 | }, 12 | "engines": { 13 | "node": ">=12" 14 | }, 15 | "scripts": { 16 | "start": "node app.js", 17 | "test": "mocha --exit test/*.test.js" 18 | }, 19 | "dependencies": { 20 | "express": "^4.16.3" 21 | }, 22 | "devDependencies": { 23 | "mocha": "^9.0.0", 24 | "uuid": "^9.0.0", 25 | "chai": "^4.2.0", 26 | "node-fetch": "^2.6.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gce/startup-script.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # Copyright 2019, Google, Inc. 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # [START startup] 16 | set -ve 17 | 18 | 19 | # Talk to the metadata server to get the project id 20 | PROJECTID=$(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google") 21 | # [END startup] 22 | echo ${PROJECTID} 23 | # [START startup] 24 | 25 | # Install logging monitor. The monitor will automatically pick up logs sent to 26 | # syslog. 27 | curl -s "https://storage.googleapis.com/signals-agents/logging/google-fluentd-install.sh" | bash 28 | service google-fluentd restart & 29 | 30 | # Install dependencies from apt 31 | apt-get update 32 | apt-get install -yq ca-certificates git build-essential supervisor 33 | 34 | # git requires $HOME and it's not set during the startup script. 35 | # Fetch source code 36 | export HOME=/root 37 | git clone https://github.com/GoogleCloudPlatform/nodejs-getting-started.git /opt/app 38 | 39 | # Install nodejs 40 | mkdir /opt/nodejs 41 | curl https://nodejs.org/dist/v22.14.0/node-v22.14.0-linux-x64.tar.xz | tar xvfJ - -C /opt/nodejs --strip-components=1 42 | ln -s /opt/nodejs/bin/node /usr/bin/node 43 | ln -s /opt/nodejs/bin/npm /usr/bin/npm 44 | 45 | # Install app dependencies 46 | cd /opt/app/gce 47 | npm install 48 | 49 | # Create a nodeapp user. The application will run as this user. 50 | useradd -m -d /home/nodeapp nodeapp 51 | chown -R nodeapp:nodeapp /opt/app 52 | 53 | # Configure supervisor to run the node app. 54 | cat >/etc/supervisor/conf.d/node-app.conf << EOF 55 | [program:nodeapp] 56 | directory=/opt/app/gce 57 | command=npm start 58 | autostart=true 59 | autorestart=true 60 | user=nodeapp 61 | environment=HOME="/home/nodeapp",USER="nodeapp",NODE_ENV="production" 62 | stdout_logfile=syslog 63 | stderr_logfile=syslog 64 | EOF 65 | 66 | supervisorctl reread 67 | supervisorctl update 68 | 69 | # Application should now be running under supervisor 70 | # [END startup] 71 | -------------------------------------------------------------------------------- /gce/test/app.test.js: -------------------------------------------------------------------------------- 1 | const cp = require('child_process'); 2 | const path = require('path'); 3 | const fetch = require('node-fetch'); 4 | const {expect} = require('chai'); 5 | const {v4: uuidv4} = require('uuid'); 6 | let testFlag = true; 7 | let uniqueID; 8 | let externalIP; 9 | 10 | async function pingVMExponential(address, count) { 11 | await new Promise((r) => setTimeout(r, Math.pow(2, count) * 1000)); 12 | try { 13 | const res = await fetch(address); 14 | if (res.status !== 200) { 15 | throw new Error(res.status); 16 | } 17 | } catch (err) { 18 | process.stdout.write('.'); 19 | await pingVMExponential(address, ++count); 20 | } 21 | } 22 | 23 | async function getIP(uniqueID) { 24 | externalIP = cp 25 | .execSync( 26 | `gcloud compute instances describe my-app-instance-${uniqueID} \ 27 | --format='get(networkInterfaces[0].accessConfigs[0].natIP)' --zone=us-central1-f` 28 | ) 29 | .toString('utf8') 30 | .trim(); 31 | 32 | await pingVMExponential(`http://${externalIP}:8080/`, 1); 33 | } 34 | 35 | describe('spin up gce instance', async function () { 36 | console.time('beforeHook'); 37 | console.time('test'); 38 | console.time('afterHook'); 39 | this.timeout(250000); 40 | uniqueID = uuidv4().split('-')[0]; 41 | before(async function () { 42 | this.timeout(200000); 43 | cp.execSync( 44 | `gcloud compute instances create my-app-instance-${uniqueID} \ 45 | --image-family=debian-10 \ 46 | --image-project=debian-cloud \ 47 | --machine-type=g1-small \ 48 | --scopes userinfo-email,cloud-platform \ 49 | --metadata app-location=us-central1-f \ 50 | --metadata-from-file startup-script=gce/startup-script.sh \ 51 | --zone us-central1-f \ 52 | --tags http-server`, 53 | {cwd: path.join(__dirname, '../../')} 54 | ); 55 | cp.execSync(`gcloud compute firewall-rules create default-allow-http-8080-${uniqueID} \ 56 | --allow tcp:8080 \ 57 | --source-ranges 0.0.0.0/0 \ 58 | --target-tags http-server \ 59 | --description "Allow port 8080 access to http-server"`); 60 | 61 | try { 62 | const timeOutPromise = new Promise((resolve, reject) => { 63 | setTimeout(() => reject('Timed out!'), 90000); 64 | }); 65 | await Promise.race([timeOutPromise, getIP(uniqueID)]); 66 | } catch (err) { 67 | testFlag = false; 68 | } 69 | console.timeEnd('beforeHook'); 70 | }); 71 | 72 | after(function () { 73 | try { 74 | cp.execSync( 75 | `gcloud compute instances delete my-app-instance-${uniqueID} --zone=us-central1-f --delete-disks=all` 76 | ); 77 | } catch (err) { 78 | console.log("wasn't able to delete the instance"); 79 | } 80 | console.timeEnd('afterHook'); 81 | }); 82 | 83 | it('should get the instance', async () => { 84 | if (testFlag) { 85 | console.log(`http://${externalIP}:8080/`); 86 | const response = await fetch(`http://${externalIP}:8080/`); 87 | const body = await response.text(); 88 | expect(body).to.include('Hello, world!'); 89 | } 90 | console.timeEnd('test'); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-getting-started", 3 | "private": true, 4 | "description": "End to end samples for running Node.js applications on Google Cloud Platform", 5 | "dependencies": {}, 6 | "devDependencies": { 7 | "eslint": "^8.0.0", 8 | "eslint-config-prettier": "^8.0.0", 9 | "eslint-plugin-node": "^11.1.0", 10 | "eslint-plugin-prettier": "^4.0.0", 11 | "prettier": "^2.0.0" 12 | }, 13 | "scripts": { 14 | "lint": "eslint '**/*.js'", 15 | "fix": "npm run lint -- --fix" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/GoogleCloudPlatform/nodejs-getting-started.git" 20 | }, 21 | "author": "Google LLC", 22 | "license": "Apache-2.0", 23 | "bugs": { 24 | "url": "https://github.com/GoogleCloudPlatform/nodejs-getting-started/issues" 25 | }, 26 | "homepage": "https://github.com/GoogleCloudPlatform/nodejs-getting-started#readme" 27 | } 28 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | "docker:disable", 5 | ":disableDependencyDashboard" 6 | 7 | ], 8 | "pinVersions": false, 9 | "rebaseStalePrs": true 10 | } 11 | -------------------------------------------------------------------------------- /sessions/app.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2019, Google LLC. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # 14 | 15 | #[START getting_started_sessions_runtime] 16 | runtime: nodejs10 17 | #[END getting_started_sessions_runtime] 18 | 19 | -------------------------------------------------------------------------------- /sessions/index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Google LLC. 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | const {Firestore} = require('@google-cloud/firestore'); 15 | const express = require('express'); 16 | const session = require('express-session'); 17 | 18 | const app = express(); 19 | const {FirestoreStore} = require('@google-cloud/connect-firestore'); 20 | 21 | app.use( 22 | session({ 23 | store: new FirestoreStore({ 24 | dataset: new Firestore(), 25 | kind: 'express-sessions', 26 | }), 27 | secret: 'my-secret', 28 | resave: false, 29 | saveUninitialized: true, 30 | }) 31 | ); 32 | 33 | const greetings = [ 34 | 'Hello World', 35 | 'Hallo Welt', 36 | 'Ciao Mondo', 37 | 'Salut le Monde', 38 | 'Hola Mundo', 39 | ]; 40 | 41 | app.get('/', (req, res) => { 42 | if (!req.session.views) { 43 | req.session.views = 0; 44 | req.session.greeting = 45 | greetings[Math.floor(Math.random() * greetings.length)]; 46 | } 47 | const views = req.session.views++; 48 | res.send(`${views} views for ${req.session.greeting}`); 49 | }); 50 | 51 | const port = process.env.PORT || 8080; 52 | app.listen(port, () => { 53 | console.log(`Example app listening on port ${port}!`); 54 | }); 55 | 56 | module.exports = app; 57 | -------------------------------------------------------------------------------- /sessions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "session-handling", 3 | "private": true, 4 | "description": "Session Handling via Firestore Tutorial", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "test": "mocha --exit **/*.test.js" 9 | }, 10 | "author": "Google Inc.", 11 | "license": "Apache-2.0", 12 | "dependencies": { 13 | "@google-cloud/connect-firestore": "^2.0.0", 14 | "@google-cloud/firestore": "^5.0.0", 15 | "express": "^4.17.1", 16 | "express-session": "^1.16.2" 17 | }, 18 | "devDependencies": { 19 | "mocha": "^9.0.0", 20 | "supertest": "^6.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sessions/test/index.test.js: -------------------------------------------------------------------------------- 1 | const app = require('../index'); 2 | 3 | const request = require('supertest'); 4 | 5 | describe('Requests have valid status codes', () => { 6 | it('should get 200', (done) => { 7 | request(app).get('/').expect(200, done); 8 | }); 9 | }); 10 | --------------------------------------------------------------------------------