@adobe/helix-google-support 1.0.0 → 1.0.1

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/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [1.0.1](https://github.com/adobe/helix-google-support/compare/v1.0.0...v1.0.1) (2022-04-20)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * minor update ([#6](https://github.com/adobe/helix-google-support/issues/6)) ([8d7cd50](https://github.com/adobe/helix-google-support/commit/8d7cd50c8162da12c4d4430833416fac0f2eda93))
7
+
1
8
  # 1.0.0 (2022-04-13)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/helix-google-support",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Helix Google Support",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -47,9 +47,10 @@
47
47
  "eslint-plugin-import": "2.26.0",
48
48
  "husky": "7.0.4",
49
49
  "junit-report-builder": "3.0.0",
50
- "lint-staged": "12.3.7",
50
+ "lint-staged": "12.3.8",
51
51
  "mocha": "9.2.2",
52
52
  "mocha-multi-reporters": "1.5.1",
53
+ "nock": "13.2.4",
53
54
  "semantic-release": "19.0.2"
54
55
  },
55
56
  "lint-staged": {
@@ -10,14 +10,49 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
  import { google } from 'googleapis';
13
- import { editDistance, sanitizeName, splitByExtension } from '@adobe/helix-onedrive-support/utils';
13
+ import {
14
+ editDistance, sanitizeName, splitByExtension,
15
+ } from '@adobe/helix-onedrive-support/utils';
16
+ import { StatusCodeError } from '@adobe/helix-onedrive-support';
14
17
  import { GoogleTokenCache } from './GoogleTokenCache.js';
15
18
  import cache from './cache.js';
16
19
 
20
+ /**
21
+ * @typedef DriveItemInfo {
22
+ * @property {string} id
23
+ * @property {string} name
24
+ * @property {string} url
25
+ * @property {string} path
26
+ */
27
+
28
+ /**
29
+ * Adds the last modified property if defined in item
30
+ * @param {DriveItemInfo} itemInfo
31
+ * @param item
32
+ * @returns {DriveItemInfo}
33
+ */
34
+ function addLastModified(itemInfo, item) {
35
+ if (item.modifiedTime) {
36
+ // eslint-disable-next-line no-param-reassign
37
+ itemInfo.lastModified = Date.parse(item.modifiedTime);
38
+ }
39
+ return itemInfo;
40
+ }
41
+
17
42
  /**
18
43
  * Google auth client
19
44
  */
20
45
  export class GoogleClient {
46
+ /**
47
+ * Sets the global item cache options. It internally creates a new LRU
48
+ * with the given options
49
+ *
50
+ * @param opts
51
+ */
52
+ static setItemCacheOptions(opts) {
53
+ cache.options(opts);
54
+ }
55
+
21
56
  /**
22
57
  * Returns a url for a google drive id.
23
58
  * @param {string} id
@@ -35,22 +70,26 @@ export class GoogleClient {
35
70
  * @param {ICachePlugin} plugin
36
71
  */
37
72
  constructor(opts) {
38
- this.log = opts.log;
39
- this.auth = new google.auth.OAuth2(
40
- opts.clientId,
41
- opts.clientSecret,
42
- opts.redirectUri,
43
- );
44
- this.cachePlugin = opts.cachePlugin;
45
- this.cache = new GoogleTokenCache(opts.cachePlugin).withLog(opts.log);
73
+ Object.assign(this, {
74
+ log: opts.log,
75
+ auth: new google.auth.OAuth2(
76
+ opts.clientId,
77
+ opts.clientSecret,
78
+ opts.redirectUri,
79
+ ),
80
+ });
46
81
 
47
- /// hack to capture tokens, since the emit handler is not awaited in the google client
48
- const originalRefreshTokenNoCache = this.auth.refreshTokenNoCache.bind(this.auth);
49
- this.auth.refreshTokenNoCache = async (...args) => {
50
- const ret = await originalRefreshTokenNoCache(...args);
51
- await this.cache.store(ret.tokens);
52
- return ret;
53
- };
82
+ if (opts.cachePlugin) {
83
+ this.cachePlugin = opts.cachePlugin;
84
+ this.cache = new GoogleTokenCache(opts.cachePlugin).withLog(opts.log);
85
+ /// hack to capture tokens, since the emit handler is not awaited in the google client
86
+ const originalRefreshTokenNoCache = this.auth.refreshTokenNoCache.bind(this.auth);
87
+ this.auth.refreshTokenNoCache = async (...args) => {
88
+ const ret = await originalRefreshTokenNoCache(...args);
89
+ await this.cache.store(ret.tokens);
90
+ return ret;
91
+ };
92
+ }
54
93
 
55
94
  this.drive = google.drive({
56
95
  version: 'v3',
@@ -58,16 +97,18 @@ export class GoogleClient {
58
97
  });
59
98
 
60
99
  /**
61
- * Cached version of `getUncachedItemsFromPath`
100
+ * Cached version of `getUncachedItemsFromSegments`
62
101
  */
63
- this.getDriveItemsFromPath = cache(this.getUncachedItemsFromPath.bind(this), {
64
- hash: (fn, path, parentId) => `${parentId}:${path}`,
102
+ this.getDriveItemsFromSegments = cache(this.getUncachedItemsFromSegments.bind(this), {
103
+ hash: (fn, segs, parentId) => `${parentId}:${segs.join('/')}`,
65
104
  });
66
105
  }
67
106
 
68
107
  async init() {
69
- await this.cache.load();
70
- this.auth.setCredentials(this.cache.tokens);
108
+ if (this.cache) {
109
+ await this.cache.load();
110
+ this.auth.setCredentials(this.cache.tokens);
111
+ }
71
112
  return this;
72
113
  }
73
114
 
@@ -75,28 +116,40 @@ export class GoogleClient {
75
116
  return this.auth.generateAuthUrl(...args);
76
117
  }
77
118
 
119
+ /**
120
+ * Sets the credentials
121
+ * @param tokens
122
+ * @returns {Promise<void>}
123
+ */
78
124
  async setCredentials(tokens) {
79
- await this.cache.store(tokens);
125
+ if (this.cache) {
126
+ await this.cache.store(tokens);
127
+ }
80
128
  this.auth.setCredentials(tokens);
81
129
  }
82
130
 
131
+ /**
132
+ * Returns the token for the given code
133
+ * @param {string} code
134
+ * @returns {Promise<*>}
135
+ */
83
136
  async getToken(code) {
84
- const resp = await this.auth.getToken(code);
85
- await this.cache.store(resp.tokens);
86
- return resp;
137
+ const { tokens } = await this.auth.getToken(code);
138
+ if (this.cache) {
139
+ await this.cache.store(tokens);
140
+ }
141
+ return tokens;
87
142
  }
88
143
 
89
144
  /**
90
- * @param {Drive} drive
91
- * @param {AdminContext} context
92
145
  * @param {string} path
93
146
  * @param {string} parentId
94
147
  * @param {string} parentPath
95
- * @returns {Promise<EditFolderInfo[]>}
148
+ * @returns {Promise<DriveItemInfo[]>}
96
149
  */
97
- async getUncachedItemsFromPath(path, parentId, parentPath) {
150
+ async getUncachedItemsFromSegments(pathSegments, parentId, parentPath) {
98
151
  const { log, drive } = this;
99
- const [name, ...rest] = path.split('/');
152
+ const name = pathSegments.shift();
100
153
  const [baseName, ext] = splitByExtension(name);
101
154
  const sanitizedName = sanitizeName(baseName);
102
155
 
@@ -106,7 +159,7 @@ export class GoogleClient {
106
159
  `'${parentId}' in parents`,
107
160
  'and trashed=false',
108
161
  // folder if path continues, sheet otherwise
109
- `and mimeType ${rest.length ? '=' : '!='} 'application/vnd.google-apps.folder'`,
162
+ `and mimeType ${pathSegments.length ? '=' : '!='} 'application/vnd.google-apps.folder'`,
110
163
  ].join(' '),
111
164
  fields: 'nextPageToken, files(id, name, modifiedTime)',
112
165
  includeItemsFromAllDrives: true,
@@ -163,35 +216,39 @@ export class GoogleClient {
163
216
  }
164
217
 
165
218
  const itemPath = `${parentPath}/${sanitizedName}`;
166
- const children = rest.length
219
+ const children = pathSegments.length
167
220
  // eslint-disable-next-line no-use-before-define
168
- ? await this.getDriveItemsFromPath(rest.join('/'), item.id, itemPath)
221
+ ? await this.getDriveItemsFromSegments(pathSegments, item.id, itemPath)
169
222
  : [];
170
223
 
171
224
  if (!children) {
172
225
  return null;
173
226
  }
174
227
 
175
- const pathItem = {
228
+ const pathItem = addLastModified({
176
229
  name,
177
230
  path: itemPath,
178
231
  id: item.id,
179
- lastModified: Date.parse(item.modifiedTime),
180
- };
232
+ }, item);
181
233
 
182
234
  return [...children, pathItem];
183
235
  }
184
236
 
185
237
  /**
186
238
  * returns the items hierarchy for the given path and root id, starting with the given path.
187
- * @param context
188
239
  * @param path
189
240
  * @param rootId
241
+ * @return {DriveItemInfo[]}
190
242
  */
191
243
  async getItemsFromPath(path, rootId) {
192
- const result = await this.getDriveItemsFromPath(path, rootId, '');
244
+ const segs = path.split('/');
245
+ if (!segs[0]) {
246
+ // if path starts with '/' the first segment is empty
247
+ segs.shift();
248
+ }
249
+ const result = await this.getDriveItemsFromSegments(segs, rootId, '');
193
250
  if (!result) {
194
- return null;
251
+ return [];
195
252
  }
196
253
  return [...result, {
197
254
  name: '',
@@ -202,11 +259,9 @@ export class GoogleClient {
202
259
 
203
260
  /**
204
261
  * returns the items hierarchy for the given item, starting with the given id
205
- * @param {AdminContext} context
206
- * @param {Drive} drive
207
262
  * @param {string} fileId
208
263
  * @param {object} roots
209
- * @returns {Promise<EditFolderInfo[]>}
264
+ * @returns {Promise<DriveItemInfo[]>}
210
265
  */
211
266
  async getItems(fileId, roots) {
212
267
  const { log } = this;
@@ -223,62 +278,84 @@ export class GoogleClient {
223
278
  const root = roots[fileId];
224
279
  if (root) {
225
280
  // stop at mount root
226
- return [{
281
+ return [addLastModified({
227
282
  id: fileId,
228
283
  name: '',
229
284
  path: root,
230
- lastModified: Date.parse(data.modifiedTime),
231
- }];
285
+ }, data)];
232
286
  }
233
287
 
234
288
  const parentId = data.parents ? data.parents[0] : '';
235
289
  if (!parentId) {
236
290
  // outside mountpoint
237
- return [{
291
+ return [addLastModified({
238
292
  id: fileId,
239
293
  name: data.name,
240
294
  path: `/root:/${data.name}`,
241
- lastModified: Date.parse(data.modifiedTime),
242
- }];
295
+ }, data)];
243
296
  }
244
297
 
245
298
  const ancestors = await this.getItems(data.parents[0], roots);
246
299
  const parentPath = ancestors[0].path.replace(/\/+$/, '');
247
- ancestors.unshift({
300
+ ancestors.unshift(addLastModified({
248
301
  id: fileId,
249
302
  name: data.name,
250
303
  path: `${parentPath}/${data.name}`,
251
- lastModified: Date.parse(data.modifiedTime),
252
- });
304
+ }, data));
253
305
  return ancestors;
254
306
  }
255
307
 
256
308
  /**
257
- * @param {AdminContext} context
258
309
  * @param {string} fileId
259
310
  * @param {object} roots
260
- * @returns {Promise<EditFolderInfo[]>}
311
+ * @returns {Promise<DriveItemInfo[]>}
261
312
  */
262
313
  async getItemsFromId(fileId, roots) {
263
314
  const { log } = this;
264
315
  try {
265
316
  return await this.getItems(fileId, roots);
266
317
  } catch (e) {
318
+ if (e.response && e.response.status === 404) {
319
+ log.warn(`unable to get items for ${fileId}. Not found`);
320
+ return [];
321
+ }
267
322
  log.warn(`unable to get items for ${fileId}. ${e}`);
268
- return [];
323
+ throw e;
269
324
  }
270
325
  }
271
326
 
272
327
  /**
273
- * @param {AdminContext} context
274
328
  * @param {string} fileId
275
329
  * @returns {string} file data
276
330
  */
277
331
  async getFile(fileId) {
278
- const res = await this.drive.files.get({
279
- fileId,
280
- alt: 'media',
332
+ try {
333
+ const res = await this.drive.files.get({
334
+ fileId,
335
+ alt: 'media',
336
+ });
337
+ return res.data;
338
+ } catch (e) {
339
+ if (e.response && e.response.status === 404) {
340
+ throw new StatusCodeError(`Not Found: ${fileId}`, 404);
341
+ }
342
+ throw e;
343
+ }
344
+ }
345
+
346
+ async getDocument(documentId) {
347
+ const docs = google.docs({
348
+ version: 'v1',
349
+ auth: this.auth,
281
350
  });
282
- return res.data;
351
+ try {
352
+ const res = await docs.documents.get({ documentId });
353
+ return res.data;
354
+ } catch (e) {
355
+ if (e.response && e.response.status === 404) {
356
+ throw new StatusCodeError(`Not Found: ${documentId}`, 404);
357
+ }
358
+ throw e;
359
+ }
283
360
  }
284
361
  }