├── .gitignore ├── DataFire.yml ├── README.md ├── create.js ├── package.json ├── retrieve.js └── spreadsheet.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | DataFire-accounts.yml 3 | -------------------------------------------------------------------------------- /DataFire.yml: -------------------------------------------------------------------------------- 1 | paths: 2 | /pets: 3 | get: 4 | action: ./retrieve 5 | post: 6 | action: ./create 7 | /pets/{id}: 8 | get: 9 | action: ./retrieve 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Create an API backed by Google Sheets 2 | Use this repo to create a quick-and-dirty API that will store data in Google Sheets. 3 | 4 | ## Setup 5 | First clone and install 6 | ``` 7 | git clone https://github.com/DataFire-flows/sheets-api && cd sheets-api 8 | npm install 9 | npm install -g datafire 10 | ``` 11 | 12 | ### Authenticate 13 | Use `datafire authenticate` to add your credentials. 14 | [See below](https://github.com/DataFire-flows/sheets-api#creating-a-google-sheets-client) 15 | for instructions on getting your Google Sheets credentials 16 | 17 | ``` 18 | datafire authenticate google_sheets --alias sheetsOwner 19 | ``` 20 | 21 | ### Create a new sheet 22 | You can manually create a new sheet at sheets.google.com, or 23 | run this command, replacing "Pet Store" with your title 24 | ``` 25 | datafire run google_sheets/spreadsheets.create \ 26 | --input.body.properties.title "Pet Store" \ 27 | --accounts.google_sheets sheetsOwner 28 | 29 | # spreadsheetId: abcd 30 | # properties: 31 | # title: Pet Store 32 | # ... 33 | ``` 34 | 35 | Copy `spreadsheetId` from the response (or the sheets.google.com URL) and paste it into `spreadsheet.js`: 36 | 37 | ```js 38 | spreadsheet.id = "abcd"; 39 | ``` 40 | 41 | ## Running 42 | Start the server with `datafire serve`: 43 | ``` 44 | datafire serve --port 3000 & 45 | ``` 46 | #### View pets 47 | ``` 48 | # Get the first 10 results 49 | curl http://localhost:3000/pets 50 | 51 | # Get the second page 52 | curl http://localhost:3000/pets?page=2 53 | 54 | # Get pet #12 55 | curl http://localhost:3000/pets/12 56 | ``` 57 | 58 | #### Add a pet 59 | ``` 60 | curl -X POST -d '{"name": "Lucy", "age": 2}' \ 61 | http://localhost:3000/pets 62 | ``` 63 | 64 | #### Stop the server 65 | ``` 66 | kill $! 67 | ``` 68 | 69 | ## Modify the API 70 | The field names and validation info are all in [spreadsheet.js](./spreadsheet.js). You can modify 71 | that file to change the API. 72 | 73 | You can also change the URL from `/pets` to something new by editing DataFire.yml. 74 | 75 | ## Creating a Google Sheets client 76 | To register a Google Sheets client, visit 77 | [console.developers.google.com](https://console.developers.google.com/apis/api/sheets.googleapis.com/overview) 78 | * click "Enable API" 79 | * click "Credentials" 80 | * click "Create Credentials" -> "OAuth Client ID" 81 | * Choose "web application" 82 | * add `http://localhost:3000` as an "Authorized redirect URI" 83 | -------------------------------------------------------------------------------- /create.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let datafire = require('datafire'); 4 | let spreadsheet = require('./spreadsheet'); 5 | let sheets = require('@datafire/google_sheets').actions; 6 | let retrieve = require('./retrieve'); 7 | 8 | module.exports = new datafire.Action({ 9 | inputs: spreadsheet.fields, 10 | handler: (input, context) => { 11 | context.accounts.google_sheets = context.accounts.sheetsOwner; 12 | let row = spreadsheet.fields.map(f => input[f.title]); 13 | return datafire.flow(context) 14 | .then(_ => { 15 | return sheets.spreadsheets.values.append({ 16 | spreadsheetId: spreadsheet.id, 17 | range: 'A1:A' + spreadsheet.fields.length, 18 | valueInputOption: 'USER_ENTERED', 19 | body: { 20 | values: [row], 21 | } 22 | }, context) 23 | }) 24 | .then(item => { 25 | let range = item.updates.updatedRange; 26 | let id = spreadsheet.getRowFromRange(range); 27 | return id; 28 | }) 29 | .then(id => { 30 | return retrieve.run({id}, context); 31 | }) 32 | } 33 | }) 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sheets-api", 3 | "version": "1.0.0", 4 | "description": "Use this repo to create a quick-and-dirty API that will store data in Google Sheets. The API can be easily run on AWS Lambda with API Gateway.", 5 | "main": "create.js", 6 | "dependencies": { 7 | "@datafire/google_sheets": "^2.0.0", 8 | "datafire": "^2.0.2" 9 | }, 10 | "devDependencies": {}, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/DataFire-flows/sheets-api.git" 17 | }, 18 | "author": "", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/DataFire-flows/sheets-api/issues" 22 | }, 23 | "homepage": "https://github.com/DataFire-flows/sheets-api#readme" 24 | } 25 | -------------------------------------------------------------------------------- /retrieve.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let datafire = require('datafire'); 4 | let spreadsheet = require('./spreadsheet'); 5 | let sheets = require('@datafire/google_sheets').actions; 6 | 7 | const PAGE_SIZE = 10; 8 | 9 | module.exports = new datafire.Action({ 10 | inputs: [{ 11 | title: 'id', 12 | type: 'integer', 13 | default: 0, 14 | description: "The ID of the item to get", 15 | }, { 16 | title: 'page', 17 | type: 'integer', 18 | minimum: 1, 19 | default: 1, 20 | }], 21 | inputSchema: { 22 | properties: { 23 | id: {type: 'integer', minimum: 1}, 24 | page: {type: 'integer', minimum: 1, default: 1}, 25 | }, 26 | }, 27 | accounts: { 28 | sheetsOwner: {integration: 'sheets'}, 29 | }, 30 | handler: (input, context) => { 31 | return datafire.flow(context) 32 | .then(_ => { 33 | let startRow = 1; 34 | let endRow = 1; 35 | if (input.id) { 36 | startRow = input.id; 37 | endRow = startRow + 1; 38 | } else { 39 | let page = input.page; 40 | startRow = (page - 1) * PAGE_SIZE + 1; 41 | endRow = startRow + PAGE_SIZE; 42 | } 43 | let lastCol = spreadsheet.getColumn(spreadsheet.fields.length - 1); 44 | let ranges = []; 45 | for (let i = startRow; i < endRow; ++i) { 46 | ranges.push('A' + i + ':' + lastCol + i); 47 | } 48 | context.accounts.google_sheets = context.accounts.sheetsOwner; 49 | return sheets.spreadsheets.values.batchGet({ 50 | spreadsheetId: spreadsheet.id, 51 | ranges: ranges, 52 | valueRenderOption: 'UNFORMATTED_VALUE', 53 | }, context) 54 | }) 55 | .then(rows => { 56 | let pets = rows.valueRanges 57 | .filter(range => range.values) 58 | .map(range => { 59 | let pet = {id: spreadsheet.getRowFromRange(range.range)}; 60 | spreadsheet.fields.forEach((field, idx) => { 61 | pet[field.title] = range.values[0][idx] 62 | }); 63 | return pet; 64 | }); 65 | if (!pets.length && input.id) { 66 | return new datafire.Response({statusCode: 404, body: "Pet " + input.id + " not found"}) 67 | } 68 | return input.id ? pets[0] : pets; 69 | }) 70 | } 71 | }); 72 | 73 | -------------------------------------------------------------------------------- /spreadsheet.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let spreadsheet = module.exports = {}; 4 | 5 | spreadsheet.id="12l8GpqPRbweYf-1DApcp3lBJ-btpA8yTr2GCMnZ1FQY"; 6 | 7 | spreadsheet.fields = [{ 8 | title: 'name', 9 | type: 'string', 10 | pattern: "^\\w{2,30}$", 11 | }, { 12 | title: 'age', 13 | type: 'integer', 14 | minimum: 0, 15 | }, { 16 | title: 'animal_type', 17 | type: 'string', 18 | enum: ['', 'dog', 'cat', 'gerbil'], 19 | default: '', 20 | }]; 21 | 22 | spreadsheet.getColumn = idx => { 23 | if (idx >= 26) return spreadsheet.getColumn(Math.floor(idx / 26) - 1) + spreadsheet.getColumn(idx % 26) 24 | return String.fromCharCode(idx + 65); 25 | } 26 | spreadsheet.getRowFromRange = range => { 27 | let rowNumber = +range.match(/A(\d+):\w+\d+/)[1]; 28 | if (!rowNumber) throw new Error("Couldn't match row number:" + range); 29 | return rowNumber; 30 | } 31 | --------------------------------------------------------------------------------