@ianmaleney/sheets-api-helper 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTH_IMPLEMENTATION.md +109 -0
- package/README.md +185 -0
- package/SETUP.md +114 -0
- package/example.js +59 -0
- package/index.js +105 -0
- package/index.test.js +23 -0
- package/package.json +21 -0
- package/setup-check.js +89 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Google Sheets API Authentication Flow - Implementation Summary
|
|
2
|
+
|
|
3
|
+
## ✅ What's Been Created
|
|
4
|
+
|
|
5
|
+
### 1. **Production-Ready Authentication System** ([index.js](index.js))
|
|
6
|
+
|
|
7
|
+
#### Core Functions:
|
|
8
|
+
|
|
9
|
+
**`initializeAuth()`**
|
|
10
|
+
- Initializes the Google Sheets API client using service account credentials
|
|
11
|
+
- Loads credentials from `service_key.json`
|
|
12
|
+
- Implements caching to avoid re-authentication
|
|
13
|
+
- Error handling with clear error messages
|
|
14
|
+
|
|
15
|
+
**`getSheetData(spreadsheetId, range)`**
|
|
16
|
+
- Fetches data from a single spreadsheet range
|
|
17
|
+
- Automatically initializes auth if needed
|
|
18
|
+
- Returns rows as an array
|
|
19
|
+
- Returns empty array if no data found
|
|
20
|
+
- Includes error handling
|
|
21
|
+
|
|
22
|
+
**`getMultipleRanges(spreadsheetId, ranges)`**
|
|
23
|
+
- Efficiently fetches multiple ranges in one API call
|
|
24
|
+
- Returns object with ranges as keys
|
|
25
|
+
- Better performance for bulk reads
|
|
26
|
+
|
|
27
|
+
### 2. **Setup Guide** ([SETUP.md](SETUP.md))
|
|
28
|
+
Comprehensive documentation including:
|
|
29
|
+
- Prerequisites
|
|
30
|
+
- Step-by-step setup instructions
|
|
31
|
+
- How to create a service account
|
|
32
|
+
- How to share spreadsheets with the service account
|
|
33
|
+
- Usage examples
|
|
34
|
+
- Troubleshooting guide
|
|
35
|
+
|
|
36
|
+
### 3. **Verification Script** ([setup-check.js](setup-check.js))
|
|
37
|
+
Automated setup checker that verifies:
|
|
38
|
+
- ✓ service_key.json exists and is valid
|
|
39
|
+
- ✓ Environment variables are set
|
|
40
|
+
- ✓ Dependencies are installed
|
|
41
|
+
- ✓ .gitignore is properly configured
|
|
42
|
+
|
|
43
|
+
### 4. **Tests** ([index.test.js](index.test.js))
|
|
44
|
+
- Runs authentication before tests
|
|
45
|
+
- Integration test that verifies API connectivity
|
|
46
|
+
- Uses Bun's test framework
|
|
47
|
+
|
|
48
|
+
### 5. **Security** ([.gitignore](.gitignore))
|
|
49
|
+
- Added `service_key.json` to .gitignore
|
|
50
|
+
- Added credential files to ignore list
|
|
51
|
+
- Prevents accidentally committing sensitive data
|
|
52
|
+
|
|
53
|
+
## 🚀 How to Use
|
|
54
|
+
|
|
55
|
+
### Initial Setup
|
|
56
|
+
```bash
|
|
57
|
+
# 1. Add your service account key
|
|
58
|
+
cp your-service-account-key.json service_key.json
|
|
59
|
+
|
|
60
|
+
# 2. Update environment variables
|
|
61
|
+
echo "TEST_SHEET_ID=your_spreadsheet_id" >> .env
|
|
62
|
+
|
|
63
|
+
# 3. Share the spreadsheet with the service account email
|
|
64
|
+
# (Email is in service_key.json under "client_email")
|
|
65
|
+
|
|
66
|
+
# 4. Verify setup
|
|
67
|
+
node setup-check.js
|
|
68
|
+
|
|
69
|
+
# 5. Run tests
|
|
70
|
+
bun test
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### In Your Code
|
|
74
|
+
```javascript
|
|
75
|
+
import { initializeAuth, getSheetData } from './index.js';
|
|
76
|
+
|
|
77
|
+
// Initialize once at app startup
|
|
78
|
+
await initializeAuth();
|
|
79
|
+
|
|
80
|
+
// Then fetch data anytime
|
|
81
|
+
const rows = await getSheetData('spreadsheet-id', 'Sheet1!A1:D10');
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 🔑 Key Features
|
|
85
|
+
|
|
86
|
+
- **Service Account Authentication**: Uses service accounts for secure, non-interactive access
|
|
87
|
+
- **Automatic Initialization**: Auth is automatically initialized on first data fetch
|
|
88
|
+
- **Caching**: Client is cached to avoid re-authentication
|
|
89
|
+
- **Error Handling**: Clear error messages for debugging
|
|
90
|
+
- **Batch Fetching**: Support for multiple ranges in one call
|
|
91
|
+
- **Type Safety**: JSDoc comments for all functions
|
|
92
|
+
|
|
93
|
+
## ⚠️ Common Issues & Solutions
|
|
94
|
+
|
|
95
|
+
**"The caller does not have permission"**
|
|
96
|
+
- Share the spreadsheet with the service account email (found in service_key.json)
|
|
97
|
+
|
|
98
|
+
**"Service key not found"**
|
|
99
|
+
- Ensure service_key.json is in the project root
|
|
100
|
+
|
|
101
|
+
**Slow Initial Load**
|
|
102
|
+
- First authentication takes a few seconds - this is normal
|
|
103
|
+
|
|
104
|
+
## 📝 Next Steps
|
|
105
|
+
|
|
106
|
+
1. Follow the [SETUP.md](SETUP.md) guide to complete configuration
|
|
107
|
+
2. Run `node setup-check.js` to verify everything is set up correctly
|
|
108
|
+
3. Run `bun test` to test the connection
|
|
109
|
+
4. Start using `getSheetData()` in your application
|
package/README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Google Sheets API - Complete Authentication Flow
|
|
2
|
+
|
|
3
|
+
A production-ready authentication system for accessing Google Sheets data using service account credentials.
|
|
4
|
+
|
|
5
|
+
## 🎯 What This Provides
|
|
6
|
+
|
|
7
|
+
✅ **Service Account Authentication** - Secure, non-interactive API access
|
|
8
|
+
✅ **Automatic Initialization** - Auth is set up automatically on first use
|
|
9
|
+
✅ **Cached Client** - Avoids re-authentication for every request
|
|
10
|
+
✅ **Error Handling** - Clear error messages for debugging
|
|
11
|
+
✅ **Batch Operations** - Fetch multiple ranges efficiently
|
|
12
|
+
✅ **Integration Tests** - Verify API connectivity
|
|
13
|
+
✅ **Setup Verification** - Script to check your configuration
|
|
14
|
+
|
|
15
|
+
## 📁 Project Structure
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
.
|
|
19
|
+
├── index.js # Main API implementation
|
|
20
|
+
├── index.test.js # Integration tests
|
|
21
|
+
├── example.js # Usage examples
|
|
22
|
+
├── setup-check.js # Configuration verification script
|
|
23
|
+
├── SETUP.md # Detailed setup guide
|
|
24
|
+
├── AUTH_IMPLEMENTATION.md # Implementation details
|
|
25
|
+
├── README.md # This file
|
|
26
|
+
├── .env # Environment variables (contains TEST_SHEET_ID)
|
|
27
|
+
├── .gitignore # Git ignore (keeps credentials safe)
|
|
28
|
+
├── service_key.json # Service account key (DO NOT COMMIT)
|
|
29
|
+
└── package.json # Project dependencies
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 🚀 Quick Start
|
|
33
|
+
|
|
34
|
+
### 1. Verify Setup
|
|
35
|
+
```bash
|
|
36
|
+
node setup-check.js
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This will check:
|
|
40
|
+
- ✓ service_key.json exists and is valid
|
|
41
|
+
- ✓ TEST_SHEET_ID is set in .env
|
|
42
|
+
- ✓ Dependencies are installed
|
|
43
|
+
- ✓ Credentials are not committed to git
|
|
44
|
+
|
|
45
|
+
### 2. Share Your Spreadsheet
|
|
46
|
+
|
|
47
|
+
1. Get the service account email from the output above
|
|
48
|
+
2. Open your Google Sheet
|
|
49
|
+
3. Click **Share**
|
|
50
|
+
4. Paste the service account email
|
|
51
|
+
5. Give it **Viewer** access (or **Editor** if needed)
|
|
52
|
+
|
|
53
|
+
### 3. Run Example
|
|
54
|
+
```bash
|
|
55
|
+
bun run example.js
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 4. Run Tests
|
|
59
|
+
```bash
|
|
60
|
+
bun test
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 📖 API Reference
|
|
64
|
+
|
|
65
|
+
### `initializeAuth()`
|
|
66
|
+
Initialize the Google Sheets API client. Call this once at app startup.
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
import { initializeAuth } from './index.js';
|
|
70
|
+
|
|
71
|
+
await initializeAuth();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### `getSheetData(spreadsheetId, range)`
|
|
75
|
+
Fetch data from a single spreadsheet range.
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
const rows = await getSheetData(
|
|
79
|
+
'1ZEs6v0-izIyVNFdBb7B4PXOGXsdsjuTq0tAXJBg_kDU',
|
|
80
|
+
'Sheet1!A1:D10'
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
console.log(rows); // Array of rows
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### `getMultipleRanges(spreadsheetId, ranges)`
|
|
87
|
+
Fetch multiple ranges efficiently in one API call.
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
const data = await getMultipleRanges(
|
|
91
|
+
'1ZEs6v0-izIyVNFdBb7B4PXOGXsdsjuTq0tAXJBg_kDU',
|
|
92
|
+
['Sheet1!A1:D10', 'Sheet2!A1:B5']
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
console.log(data['Sheet1!A1:D10']); // Array of rows
|
|
96
|
+
console.log(data['Sheet2!A1:B5']); // Array of rows
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 🔧 Configuration
|
|
100
|
+
|
|
101
|
+
### Environment Variables (.env)
|
|
102
|
+
```env
|
|
103
|
+
TEST_SHEET_ID=your_spreadsheet_id
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Get your spreadsheet ID from the URL:
|
|
107
|
+
```
|
|
108
|
+
https://docs.google.com/spreadsheets/d/{SPREADSHEET_ID}/edit
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Service Key (service_key.json)
|
|
112
|
+
Your Google Cloud service account JSON key. Already in `.gitignore` to prevent accidental commits.
|
|
113
|
+
|
|
114
|
+
## 🐛 Troubleshooting
|
|
115
|
+
|
|
116
|
+
### "The caller does not have permission"
|
|
117
|
+
**Cause:** Spreadsheet not shared with service account
|
|
118
|
+
|
|
119
|
+
**Solution:**
|
|
120
|
+
1. Get service account email from `service_key.json` → `client_email`
|
|
121
|
+
2. Share the spreadsheet with this email
|
|
122
|
+
3. Grant at least **Viewer** access
|
|
123
|
+
|
|
124
|
+
### "Service key not found"
|
|
125
|
+
**Cause:** `service_key.json` not in project root
|
|
126
|
+
|
|
127
|
+
**Solution:**
|
|
128
|
+
1. Download your service account key from Google Cloud Console
|
|
129
|
+
2. Save it as `service_key.json` in the project root
|
|
130
|
+
3. Never commit it (already in `.gitignore`)
|
|
131
|
+
|
|
132
|
+
### Authentication Timeout
|
|
133
|
+
**Cause:** Normal for first request
|
|
134
|
+
|
|
135
|
+
**Solution:** This is expected. Subsequent requests are faster due to caching.
|
|
136
|
+
|
|
137
|
+
### No data returned
|
|
138
|
+
**Cause:** Empty sheet or invalid range
|
|
139
|
+
|
|
140
|
+
**Solution:**
|
|
141
|
+
- Check that the range contains data
|
|
142
|
+
- Verify spreadsheet ID is correct
|
|
143
|
+
- Try a different range like `Sheet1!A1:Z100`
|
|
144
|
+
|
|
145
|
+
## 📚 More Information
|
|
146
|
+
|
|
147
|
+
- **Setup Instructions**: See [SETUP.md](SETUP.md)
|
|
148
|
+
- **Implementation Details**: See [AUTH_IMPLEMENTATION.md](AUTH_IMPLEMENTATION.md)
|
|
149
|
+
- **Code Examples**: See [example.js](example.js)
|
|
150
|
+
- **Google Sheets API Docs**: [https://developers.google.com/sheets/api](https://developers.google.com/sheets/api)
|
|
151
|
+
|
|
152
|
+
## 🔐 Security
|
|
153
|
+
|
|
154
|
+
- ✓ `service_key.json` is in `.gitignore`
|
|
155
|
+
- ✓ Credentials never logged in production
|
|
156
|
+
- ✓ Uses Google Cloud's official libraries
|
|
157
|
+
- ✓ Service account is recommended over user authentication
|
|
158
|
+
- ✓ Use environment variables for sensitive data
|
|
159
|
+
|
|
160
|
+
## 🔄 Workflow Summary
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
// 1. Import once in your app
|
|
164
|
+
import { initializeAuth, getSheetData } from './index.js';
|
|
165
|
+
|
|
166
|
+
// 2. Initialize once at startup
|
|
167
|
+
await initializeAuth();
|
|
168
|
+
|
|
169
|
+
// 3. Use anytime, anywhere
|
|
170
|
+
const rows = await getSheetData('spreadsheet-id', 'Sheet1!A1:D10');
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## 💡 Tips
|
|
174
|
+
|
|
175
|
+
- **Batch Reads**: Use `getMultipleRanges()` instead of multiple `getSheetData()` calls
|
|
176
|
+
- **Range Format**: Use A1 notation like `Sheet1!A1:D10` or `'Sheet1'!A1:D10`
|
|
177
|
+
- **Large Datasets**: For very large sheets, consider using pagination
|
|
178
|
+
- **Caching**: The auth client is cached automatically - no need to reinitialize
|
|
179
|
+
|
|
180
|
+
## 🤝 Need Help?
|
|
181
|
+
|
|
182
|
+
1. Run `node setup-check.js` to verify configuration
|
|
183
|
+
2. Check [SETUP.md](SETUP.md) for detailed instructions
|
|
184
|
+
3. See [example.js](example.js) for working code
|
|
185
|
+
4. Review error messages - they indicate the exact issue
|
package/SETUP.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Google Sheets API Setup Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
This project provides a working authentication flow for the Google Sheets API using service account credentials.
|
|
5
|
+
|
|
6
|
+
## Prerequisites
|
|
7
|
+
- A Google Cloud Project with the Sheets API enabled
|
|
8
|
+
- Service account key (JSON file)
|
|
9
|
+
|
|
10
|
+
## Setup Steps
|
|
11
|
+
|
|
12
|
+
### 1. Create a Service Account (if you haven't already)
|
|
13
|
+
|
|
14
|
+
1. Go to [Google Cloud Console](https://console.cloud.google.com)
|
|
15
|
+
2. Create a new project or select an existing one
|
|
16
|
+
3. Navigate to **Service Accounts** (Search for "Service Accounts")
|
|
17
|
+
4. Click **Create Service Account**
|
|
18
|
+
5. Fill in the service account details
|
|
19
|
+
6. Click **Create and Continue**
|
|
20
|
+
7. Grant the service account "Editor" role (or minimal "Sheets Editor" role)
|
|
21
|
+
8. Click **Create Key** and select **JSON** format
|
|
22
|
+
9. Save the JSON file as `service_key.json` in the root of this project
|
|
23
|
+
|
|
24
|
+
### 2. Share Your Spreadsheet with the Service Account
|
|
25
|
+
|
|
26
|
+
1. Open the spreadsheet you want to access
|
|
27
|
+
2. Get your spreadsheet ID from the URL: `https://docs.google.com/spreadsheets/d/{SPREADSHEET_ID}/edit`
|
|
28
|
+
3. Click **Share** on the spreadsheet
|
|
29
|
+
4. In the service account JSON file, find the `client_email` field
|
|
30
|
+
5. Share the spreadsheet with this email address
|
|
31
|
+
6. Grant it **Viewer** access (or **Editor** if you need write permissions)
|
|
32
|
+
|
|
33
|
+
### 3. Set Environment Variables
|
|
34
|
+
|
|
35
|
+
Create or update `.env` file:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
TEST_SHEET_ID=your_spreadsheet_id_here
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
### Basic Usage
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
import { initializeAuth, getSheetData } from './index.js';
|
|
47
|
+
|
|
48
|
+
// Initialize authentication (call once at app startup)
|
|
49
|
+
await initializeAuth();
|
|
50
|
+
|
|
51
|
+
// Fetch data from a specific range
|
|
52
|
+
const data = await getSheetData('your-spreadsheet-id', 'Sheet1!A1:D10');
|
|
53
|
+
console.log(data);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Get Multiple Ranges
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
import { initializeAuth, getMultipleRanges } from './index.js';
|
|
60
|
+
|
|
61
|
+
await initializeAuth();
|
|
62
|
+
|
|
63
|
+
const data = await getMultipleRanges('your-spreadsheet-id', [
|
|
64
|
+
'Sheet1!A1:D10',
|
|
65
|
+
'Sheet2!A1:B5'
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
console.log(data['Sheet1!A1:D10']);
|
|
69
|
+
console.log(data['Sheet2!A1:B5']);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Running Tests
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
bun test
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Troubleshooting
|
|
79
|
+
|
|
80
|
+
### "The caller does not have permission" Error
|
|
81
|
+
- Ensure the spreadsheet is shared with the service account email
|
|
82
|
+
- Check that the service account has at least **Viewer** access
|
|
83
|
+
|
|
84
|
+
### "Service key not found" Error
|
|
85
|
+
- Verify `service_key.json` exists in the project root
|
|
86
|
+
- Check file permissions
|
|
87
|
+
|
|
88
|
+
### Authentication Timeout
|
|
89
|
+
- The first authentication may take a few seconds
|
|
90
|
+
- Ensure your internet connection is stable
|
|
91
|
+
|
|
92
|
+
## Architecture
|
|
93
|
+
|
|
94
|
+
### `initializeAuth()`
|
|
95
|
+
- Initializes the Google Sheets API client once
|
|
96
|
+
- Uses service account credentials from `service_key.json`
|
|
97
|
+
- Caches the client for subsequent calls
|
|
98
|
+
|
|
99
|
+
### `getSheetData(spreadsheetId, range)`
|
|
100
|
+
- Fetches data from a single range
|
|
101
|
+
- Returns an array of rows
|
|
102
|
+
- Returns empty array if no data found
|
|
103
|
+
|
|
104
|
+
### `getMultipleRanges(spreadsheetId, ranges)`
|
|
105
|
+
- Fetches data from multiple ranges in one call
|
|
106
|
+
- Returns an object with ranges as keys and data as values
|
|
107
|
+
- More efficient for bulk reads
|
|
108
|
+
|
|
109
|
+
## Security Notes
|
|
110
|
+
|
|
111
|
+
- **Never commit** `service_key.json` to version control
|
|
112
|
+
- Add `service_key.json` to `.gitignore` (already done)
|
|
113
|
+
- Rotate service account keys regularly
|
|
114
|
+
- Use environment variables for sensitive data
|
package/example.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: Using the Google Sheets API
|
|
3
|
+
*
|
|
4
|
+
* Before running this file:
|
|
5
|
+
* 1. Ensure service_key.json is in the project root
|
|
6
|
+
* 2. Share your spreadsheet with the service account email
|
|
7
|
+
* 3. Set your spreadsheet ID in .env
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import 'dotenv/config';
|
|
11
|
+
import { initializeAuth, getSheetData, getMultipleRanges } from './index.js';
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
try {
|
|
15
|
+
// Initialize authentication
|
|
16
|
+
console.log('Initializing authentication...\n');
|
|
17
|
+
await initializeAuth();
|
|
18
|
+
|
|
19
|
+
// Example 1: Get data from a single range
|
|
20
|
+
console.log('📊 Fetching data from Sheet1!A1:D10...');
|
|
21
|
+
const data = await getSheetData(
|
|
22
|
+
process.env.TEST_SHEET_ID,
|
|
23
|
+
'Sheet1!A1:D10'
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (data.length > 0) {
|
|
27
|
+
console.log(`✓ Retrieved ${data.length} rows`);
|
|
28
|
+
console.log('First row:', data[0]);
|
|
29
|
+
} else {
|
|
30
|
+
console.log('No data found in range');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Example 2: Get multiple ranges (if you have multiple sheets)
|
|
34
|
+
// Uncomment to test:
|
|
35
|
+
/*
|
|
36
|
+
console.log('\n📊 Fetching multiple ranges...');
|
|
37
|
+
const multiData = await getMultipleRanges(
|
|
38
|
+
process.env.TEST_SHEET_ID,
|
|
39
|
+
['Sheet1!A1:D10', 'Sheet2!A1:B5']
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
console.log('Sheet1 data:', multiData['Sheet1!A1:D10']);
|
|
43
|
+
console.log('Sheet2 data:', multiData['Sheet2!A1:B5']);
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('❌ Error:', error.message);
|
|
48
|
+
|
|
49
|
+
if (error.message.includes('The caller does not have permission')) {
|
|
50
|
+
console.log('\n💡 Solution: Share your spreadsheet with this email:');
|
|
51
|
+
console.log(' sheets@eco-emissary-484012-i1.iam.gserviceaccount.com');
|
|
52
|
+
console.log(' (Email from service_key.json client_email field)');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
main();
|
package/index.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { google } from 'googleapis';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
|
|
6
|
+
const SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly'];
|
|
7
|
+
let sheetsClient = null;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Initialize the Google Sheets API client using service account credentials
|
|
11
|
+
* @returns {Promise<void>}
|
|
12
|
+
*/
|
|
13
|
+
export const initializeAuth = async () => {
|
|
14
|
+
if (sheetsClient) {
|
|
15
|
+
return; // Already initialized
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const keyPath = path.join(process.cwd(), 'service_key.json');
|
|
20
|
+
|
|
21
|
+
if (!fs.existsSync(keyPath)) {
|
|
22
|
+
throw new Error(`Service key not found at ${keyPath}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const keyData = JSON.parse(fs.readFileSync(keyPath, 'utf-8'));
|
|
26
|
+
|
|
27
|
+
const auth = new google.auth.GoogleAuth({
|
|
28
|
+
credentials: keyData,
|
|
29
|
+
scopes: SCOPES,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
sheetsClient = google.sheets({
|
|
33
|
+
version: 'v4',
|
|
34
|
+
auth: auth,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
console.log('✓ Google Sheets API authentication initialized successfully');
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('✗ Failed to initialize authentication:', error.message);
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get data from a Google Sheet
|
|
46
|
+
* @param {string} spreadsheet_id - The spreadsheet ID
|
|
47
|
+
* @param {string} range - The range to read (e.g., 'Sheet1!A1:D10')
|
|
48
|
+
* @returns {Promise<Array>} Array of rows
|
|
49
|
+
*/
|
|
50
|
+
export const getSheetData = async (spreadsheet_id, range) => {
|
|
51
|
+
// Ensure authentication is initialized
|
|
52
|
+
if (!sheetsClient) {
|
|
53
|
+
await initializeAuth();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
// Get the values from the spreadsheet.
|
|
58
|
+
const result = await sheetsClient.spreadsheets.values.get({
|
|
59
|
+
spreadsheetId: spreadsheet_id,
|
|
60
|
+
range: range,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const rows = result.data.values;
|
|
64
|
+
|
|
65
|
+
if (!rows || rows.length === 0) {
|
|
66
|
+
console.log('No data found.');
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return rows;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(`Failed to fetch sheet data: ${error.message}`);
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get multiple ranges from a Google Sheet
|
|
79
|
+
* @param {string} spreadsheet_id - The spreadsheet ID
|
|
80
|
+
* @param {Array<string>} ranges - Array of ranges to read
|
|
81
|
+
* @returns {Promise<Object>} Object with ranges as keys and data as values
|
|
82
|
+
*/
|
|
83
|
+
export const getMultipleRanges = async (spreadsheet_id, ranges) => {
|
|
84
|
+
if (!sheetsClient) {
|
|
85
|
+
await initializeAuth();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const result = await sheetsClient.spreadsheets.values.batchGet({
|
|
90
|
+
spreadsheetId: spreadsheet_id,
|
|
91
|
+
ranges: ranges,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const data = {};
|
|
95
|
+
result.data.valueRanges.forEach((range, index) => {
|
|
96
|
+
data[ranges[index]] = range.values || [];
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return data;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error(`Failed to fetch multiple ranges: ${error.message}`);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
package/index.test.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from 'bun:test';
|
|
2
|
+
import { getSheetData, initializeAuth } from './index.js';
|
|
3
|
+
import 'dotenv/config';
|
|
4
|
+
|
|
5
|
+
describe('getSheetData', () => {
|
|
6
|
+
beforeAll(async () => {
|
|
7
|
+
// Initialize authentication before running tests
|
|
8
|
+
await initializeAuth();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should return rows when data is found', async () => {
|
|
12
|
+
const result = await getSheetData(
|
|
13
|
+
process.env.TEST_SHEET_ID,
|
|
14
|
+
'Film!A1:D10'
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
console.log('Result:', result);
|
|
18
|
+
|
|
19
|
+
// Since the actual data in the sheet may vary, we will just check if result is an array.
|
|
20
|
+
expect(Array.isArray(result)).toBe(true);
|
|
21
|
+
expect(result.length).toBeGreaterThan(0);
|
|
22
|
+
});
|
|
23
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ianmaleney/sheets-api-helper",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A helper library for interacting with the Google Sheets API using Node.js.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"author": "Ian Maleney <ian@fallowmedia.com>",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@google-cloud/local-auth": "^2.1.0",
|
|
11
|
+
"dotenv": "^17.2.3",
|
|
12
|
+
"googleapis": "^105.0.0"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "bun test"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
package/setup-check.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Setup verification script for Google Sheets API
|
|
5
|
+
* Run with: node setup-check.js
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
console.log('🔍 Google Sheets API Setup Verification\n');
|
|
15
|
+
|
|
16
|
+
// Check 1: Service Key File
|
|
17
|
+
console.log('1. Checking service_key.json...');
|
|
18
|
+
const serviceKeyPath = path.join(__dirname, 'service_key.json');
|
|
19
|
+
if (fs.existsSync(serviceKeyPath)) {
|
|
20
|
+
try {
|
|
21
|
+
const keyData = JSON.parse(fs.readFileSync(serviceKeyPath, 'utf-8'));
|
|
22
|
+
console.log(' ✓ service_key.json found');
|
|
23
|
+
console.log(` ✓ Project ID: ${keyData.project_id}`);
|
|
24
|
+
console.log(` ✓ Service Account Email: ${keyData.client_email}`);
|
|
25
|
+
|
|
26
|
+
if (!keyData.private_key) {
|
|
27
|
+
console.log(' ✗ WARNING: private_key is missing from service_key.json');
|
|
28
|
+
}
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.log(' ✗ service_key.json is not valid JSON');
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
console.log(` ✗ service_key.json not found at ${serviceKeyPath}`);
|
|
34
|
+
console.log(' Run: touch service_key.json and add your credentials');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check 2: Environment Variables
|
|
38
|
+
console.log('\n2. Checking environment variables...');
|
|
39
|
+
const envPath = path.join(__dirname, '.env');
|
|
40
|
+
if (fs.existsSync(envPath)) {
|
|
41
|
+
const envContent = fs.readFileSync(envPath, 'utf-8');
|
|
42
|
+
if (envContent.includes('TEST_SHEET_ID')) {
|
|
43
|
+
console.log(' ✓ TEST_SHEET_ID is set in .env');
|
|
44
|
+
} else {
|
|
45
|
+
console.log(' ✗ TEST_SHEET_ID is not set in .env');
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
console.log(' ✗ .env file not found');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check 3: Dependencies
|
|
52
|
+
console.log('\n3. Checking dependencies...');
|
|
53
|
+
const packageJsonPath = path.join(__dirname, 'package.json');
|
|
54
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
55
|
+
const requiredDeps = ['googleapis', '@google-cloud/local-auth'];
|
|
56
|
+
const nodeModulesPath = path.join(__dirname, 'node_modules');
|
|
57
|
+
|
|
58
|
+
requiredDeps.forEach(dep => {
|
|
59
|
+
const depPath = path.join(nodeModulesPath, dep);
|
|
60
|
+
if (fs.existsSync(depPath)) {
|
|
61
|
+
console.log(` ✓ ${dep} is installed`);
|
|
62
|
+
} else {
|
|
63
|
+
console.log(` ✗ ${dep} is not installed`);
|
|
64
|
+
console.log(' Run: npm install or bun install');
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Check 4: .gitignore
|
|
69
|
+
console.log('\n4. Checking .gitignore...');
|
|
70
|
+
const gitignorePath = path.join(__dirname, '.gitignore');
|
|
71
|
+
if (fs.existsSync(gitignorePath)) {
|
|
72
|
+
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');
|
|
73
|
+
if (gitignoreContent.includes('service_key.json')) {
|
|
74
|
+
console.log(' ✓ service_key.json is in .gitignore');
|
|
75
|
+
} else {
|
|
76
|
+
console.log(' ⚠ service_key.json is NOT in .gitignore');
|
|
77
|
+
console.log(' Add this line to .gitignore: service_key.json');
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
console.log(' ⚠ .gitignore not found');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log('\n' + '='.repeat(50));
|
|
84
|
+
console.log('Next Steps:');
|
|
85
|
+
console.log('1. Ensure service_key.json is in the project root');
|
|
86
|
+
console.log('2. Share your spreadsheet with the service account email');
|
|
87
|
+
console.log('3. Set TEST_SHEET_ID in .env');
|
|
88
|
+
console.log('4. Run: bun test');
|
|
89
|
+
console.log('='.repeat(50));
|