@memberjunction/storage 3.1.0 → 3.2.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/dist/__tests__/FileStorageBase.test.d.ts +6 -0
- package/dist/__tests__/FileStorageBase.test.d.ts.map +1 -0
- package/dist/__tests__/FileStorageBase.test.js +213 -0
- package/dist/__tests__/FileStorageBase.test.js.map +1 -0
- package/dist/__tests__/util.test.d.ts +7 -0
- package/dist/__tests__/util.test.d.ts.map +1 -0
- package/dist/__tests__/util.test.js +326 -0
- package/dist/__tests__/util.test.js.map +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +28 -14
- package/dist/config.js.map +1 -1
- package/dist/drivers/AWSFileStorage.d.ts +20 -0
- package/dist/drivers/AWSFileStorage.d.ts.map +1 -1
- package/dist/drivers/AWSFileStorage.js +43 -18
- package/dist/drivers/AWSFileStorage.js.map +1 -1
- package/dist/drivers/AzureFileStorage.d.ts +1 -1
- package/dist/drivers/AzureFileStorage.d.ts.map +1 -1
- package/dist/drivers/AzureFileStorage.js +17 -17
- package/dist/drivers/AzureFileStorage.js.map +1 -1
- package/dist/drivers/BoxFileStorage.d.ts +47 -1
- package/dist/drivers/BoxFileStorage.d.ts.map +1 -1
- package/dist/drivers/BoxFileStorage.js +219 -95
- package/dist/drivers/BoxFileStorage.js.map +1 -1
- package/dist/drivers/DropboxFileStorage.d.ts +59 -0
- package/dist/drivers/DropboxFileStorage.d.ts.map +1 -1
- package/dist/drivers/DropboxFileStorage.js +314 -62
- package/dist/drivers/DropboxFileStorage.js.map +1 -1
- package/dist/drivers/GoogleDriveFileStorage.d.ts +29 -0
- package/dist/drivers/GoogleDriveFileStorage.d.ts.map +1 -1
- package/dist/drivers/GoogleDriveFileStorage.js +220 -72
- package/dist/drivers/GoogleDriveFileStorage.js.map +1 -1
- package/dist/drivers/GoogleFileStorage.d.ts.map +1 -1
- package/dist/drivers/GoogleFileStorage.js +12 -12
- package/dist/drivers/GoogleFileStorage.js.map +1 -1
- package/dist/drivers/SharePointFileStorage.d.ts +64 -5
- package/dist/drivers/SharePointFileStorage.d.ts.map +1 -1
- package/dist/drivers/SharePointFileStorage.js +265 -94
- package/dist/drivers/SharePointFileStorage.js.map +1 -1
- package/dist/generic/FileStorageBase.d.ts +79 -13
- package/dist/generic/FileStorageBase.d.ts.map +1 -1
- package/dist/generic/FileStorageBase.js +57 -12
- package/dist/generic/FileStorageBase.js.map +1 -1
- package/dist/util.d.ts +429 -11
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +677 -16
- package/dist/util.js.map +1 -1
- package/package.json +11 -5
|
@@ -107,19 +107,19 @@ class ClientCredentialsAuthProvider {
|
|
|
107
107
|
client_id: this.clientId,
|
|
108
108
|
scope: 'https://graph.microsoft.com/.default',
|
|
109
109
|
client_secret: this.clientSecret,
|
|
110
|
-
grant_type: 'client_credentials'
|
|
110
|
+
grant_type: 'client_credentials',
|
|
111
111
|
});
|
|
112
112
|
const response = await fetch(this.tokenEndpoint, {
|
|
113
113
|
method: 'POST',
|
|
114
114
|
headers: {
|
|
115
|
-
'Content-Type': 'application/x-www-form-urlencoded'
|
|
115
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
116
116
|
},
|
|
117
|
-
body: data
|
|
117
|
+
body: data,
|
|
118
118
|
});
|
|
119
119
|
if (!response.ok) {
|
|
120
120
|
throw new Error(`Failed to get access token: ${response.statusText}`);
|
|
121
121
|
}
|
|
122
|
-
const json = await response.json();
|
|
122
|
+
const json = (await response.json());
|
|
123
123
|
this.accessToken = json.access_token;
|
|
124
124
|
// Set token expiration time (subtract 5 minutes as a buffer)
|
|
125
125
|
const expiresIn = json.expires_in || 3600;
|
|
@@ -127,6 +127,121 @@ class ClientCredentialsAuthProvider {
|
|
|
127
127
|
return this.accessToken;
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Implementation of the Microsoft Graph API AuthenticationProvider interface
|
|
132
|
+
* that uses the OAuth2 refresh token flow for per-user authentication.
|
|
133
|
+
*
|
|
134
|
+
* This provider handles token acquisition using a refresh token, enabling
|
|
135
|
+
* users to access their own OneDrive/SharePoint files rather than a shared
|
|
136
|
+
* service account.
|
|
137
|
+
*
|
|
138
|
+
* @remarks
|
|
139
|
+
* This class is designed for scenarios where each user authenticates with
|
|
140
|
+
* their own Microsoft account via OAuth. The refresh token is obtained
|
|
141
|
+
* through the OAuth authorization code flow and stored per-user.
|
|
142
|
+
*/
|
|
143
|
+
class RefreshTokenAuthProvider {
|
|
144
|
+
/**
|
|
145
|
+
* Azure AD application (client) ID
|
|
146
|
+
*/
|
|
147
|
+
clientId;
|
|
148
|
+
/**
|
|
149
|
+
* Azure AD application client secret
|
|
150
|
+
*/
|
|
151
|
+
clientSecret;
|
|
152
|
+
/**
|
|
153
|
+
* OAuth2 refresh token for obtaining new access tokens
|
|
154
|
+
*/
|
|
155
|
+
refreshToken;
|
|
156
|
+
/**
|
|
157
|
+
* OAuth2 token endpoint URL
|
|
158
|
+
*/
|
|
159
|
+
tokenEndpoint;
|
|
160
|
+
/**
|
|
161
|
+
* Cached access token
|
|
162
|
+
*/
|
|
163
|
+
accessToken = null;
|
|
164
|
+
/**
|
|
165
|
+
* Expiration timestamp for the cached token
|
|
166
|
+
*/
|
|
167
|
+
tokenExpiration = null;
|
|
168
|
+
/**
|
|
169
|
+
* Callback to persist new tokens when they are refreshed
|
|
170
|
+
*/
|
|
171
|
+
onTokenRefresh;
|
|
172
|
+
/**
|
|
173
|
+
* Creates a new RefreshTokenAuthProvider instance
|
|
174
|
+
*
|
|
175
|
+
* @param clientId - The Azure AD application (client) ID
|
|
176
|
+
* @param clientSecret - The Azure AD application client secret
|
|
177
|
+
* @param refreshToken - The OAuth2 refresh token
|
|
178
|
+
* @param tenantId - The Azure AD tenant ID (use 'common' for multi-tenant)
|
|
179
|
+
* @param onTokenRefresh - Optional callback to persist new tokens
|
|
180
|
+
*/
|
|
181
|
+
constructor(clientId, clientSecret, refreshToken, tenantId = 'common', onTokenRefresh) {
|
|
182
|
+
this.clientId = clientId;
|
|
183
|
+
this.clientSecret = clientSecret;
|
|
184
|
+
this.refreshToken = refreshToken;
|
|
185
|
+
this.tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
|
|
186
|
+
this.onTokenRefresh = onTokenRefresh;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Gets an access token for Microsoft Graph API using the refresh token.
|
|
190
|
+
*
|
|
191
|
+
* This method implements the AuthenticationProvider interface required by the
|
|
192
|
+
* Microsoft Graph client. It uses the refresh token to obtain a new access token
|
|
193
|
+
* when needed, and caches the token until it expires.
|
|
194
|
+
*
|
|
195
|
+
* @returns A Promise that resolves to the access token string
|
|
196
|
+
* @throws Error if token acquisition fails
|
|
197
|
+
*/
|
|
198
|
+
async getAccessToken() {
|
|
199
|
+
if (this.accessToken && this.tokenExpiration && this.tokenExpiration > new Date()) {
|
|
200
|
+
return this.accessToken;
|
|
201
|
+
}
|
|
202
|
+
console.log('[SharePoint RefreshTokenAuth] Refreshing access token...');
|
|
203
|
+
const data = new URLSearchParams({
|
|
204
|
+
client_id: this.clientId,
|
|
205
|
+
client_secret: this.clientSecret,
|
|
206
|
+
refresh_token: this.refreshToken,
|
|
207
|
+
grant_type: 'refresh_token',
|
|
208
|
+
scope: 'https://graph.microsoft.com/Files.ReadWrite.All offline_access',
|
|
209
|
+
});
|
|
210
|
+
const response = await fetch(this.tokenEndpoint, {
|
|
211
|
+
method: 'POST',
|
|
212
|
+
headers: {
|
|
213
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
214
|
+
},
|
|
215
|
+
body: data,
|
|
216
|
+
});
|
|
217
|
+
if (!response.ok) {
|
|
218
|
+
const errorText = await response.text();
|
|
219
|
+
console.error('[SharePoint RefreshTokenAuth] Token refresh failed:', errorText);
|
|
220
|
+
throw new Error(`Failed to refresh access token: ${response.statusText}`);
|
|
221
|
+
}
|
|
222
|
+
const json = (await response.json());
|
|
223
|
+
this.accessToken = json.access_token;
|
|
224
|
+
// Set token expiration time (subtract 5 minutes as a buffer)
|
|
225
|
+
const expiresIn = json.expires_in || 3600;
|
|
226
|
+
this.tokenExpiration = new Date(Date.now() + (expiresIn - 300) * 1000);
|
|
227
|
+
console.log('[SharePoint RefreshTokenAuth] Token refreshed successfully, expires:', this.tokenExpiration);
|
|
228
|
+
// Microsoft may return a new refresh token - if so, persist it
|
|
229
|
+
if (json.refresh_token && json.refresh_token !== this.refreshToken) {
|
|
230
|
+
console.log('[SharePoint RefreshTokenAuth] New refresh token received, persisting...');
|
|
231
|
+
this.refreshToken = json.refresh_token;
|
|
232
|
+
if (this.onTokenRefresh) {
|
|
233
|
+
try {
|
|
234
|
+
await this.onTokenRefresh(json.refresh_token, json.access_token);
|
|
235
|
+
console.log('[SharePoint RefreshTokenAuth] New tokens persisted successfully');
|
|
236
|
+
}
|
|
237
|
+
catch (callbackError) {
|
|
238
|
+
console.error('[SharePoint RefreshTokenAuth] Failed to persist new tokens:', callbackError);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return this.accessToken;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
130
245
|
/**
|
|
131
246
|
* FileStorageBase implementation for Microsoft SharePoint using the Microsoft Graph API
|
|
132
247
|
*
|
|
@@ -194,38 +309,123 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
194
309
|
* Optional ID of a subfolder to use as the root folder (if specified)
|
|
195
310
|
*/
|
|
196
311
|
_rootFolderId;
|
|
312
|
+
/**
|
|
313
|
+
* OAuth2 Client ID (for per-user OAuth flow)
|
|
314
|
+
*/
|
|
315
|
+
_clientID;
|
|
316
|
+
/**
|
|
317
|
+
* OAuth2 Client Secret (for per-user OAuth flow)
|
|
318
|
+
*/
|
|
319
|
+
_clientSecret;
|
|
320
|
+
/**
|
|
321
|
+
* OAuth2 Refresh Token (for per-user OAuth flow)
|
|
322
|
+
*/
|
|
323
|
+
_refreshToken;
|
|
324
|
+
/**
|
|
325
|
+
* Azure AD Tenant ID
|
|
326
|
+
*/
|
|
327
|
+
_tenantID;
|
|
328
|
+
/**
|
|
329
|
+
* Callback for persisting refreshed tokens
|
|
330
|
+
*/
|
|
331
|
+
_onTokenRefresh;
|
|
197
332
|
/**
|
|
198
333
|
* Creates a new SharePointFileStorage instance
|
|
199
334
|
*
|
|
200
|
-
* This constructor reads
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* @throws Error if required environment variables are missing
|
|
335
|
+
* This constructor reads configuration from environment variables if available.
|
|
336
|
+
* If no environment variables are set, the provider can be initialized later
|
|
337
|
+
* via the initialize() method with OAuth credentials from the database.
|
|
204
338
|
*/
|
|
205
339
|
constructor() {
|
|
206
340
|
super();
|
|
207
341
|
// Try to get config from centralized configuration
|
|
208
342
|
const config = (0, config_1.getProviderConfig)('sharePoint');
|
|
209
|
-
// Extract values from config, fall back to env vars
|
|
210
|
-
const clientId = config?.clientID || env.get('STORAGE_SHAREPOINT_CLIENT_ID').
|
|
211
|
-
const clientSecret = config?.clientSecret || env.get('STORAGE_SHAREPOINT_CLIENT_SECRET').
|
|
212
|
-
const tenantId = config?.tenantID || env.get('STORAGE_SHAREPOINT_TENANT_ID').
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
//
|
|
216
|
-
this.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
343
|
+
// Extract values from config, fall back to env vars (don't require them - initialize() may be called later)
|
|
344
|
+
const clientId = config?.clientID || env.get('STORAGE_SHAREPOINT_CLIENT_ID').asString();
|
|
345
|
+
const clientSecret = config?.clientSecret || env.get('STORAGE_SHAREPOINT_CLIENT_SECRET').asString();
|
|
346
|
+
const tenantId = config?.tenantID || env.get('STORAGE_SHAREPOINT_TENANT_ID').asString();
|
|
347
|
+
const siteId = config?.siteID || env.get('STORAGE_SHAREPOINT_SITE_ID').asString();
|
|
348
|
+
const driveId = config?.driveID || env.get('STORAGE_SHAREPOINT_DRIVE_ID').asString();
|
|
349
|
+
// Store OAuth credentials for IsConfigured check
|
|
350
|
+
this._clientID = clientId;
|
|
351
|
+
this._clientSecret = clientSecret;
|
|
352
|
+
this._tenantID = tenantId;
|
|
353
|
+
// Only initialize if we have all required credentials (env/config-based setup)
|
|
354
|
+
if (clientId && clientSecret && tenantId && siteId && driveId) {
|
|
355
|
+
this._siteId = siteId;
|
|
356
|
+
this._driveId = driveId;
|
|
357
|
+
// Optionally set a root folder within the SharePoint drive
|
|
358
|
+
this._rootFolderId = config?.rootFolderID || env.get('STORAGE_SHAREPOINT_ROOT_FOLDER_ID').asString();
|
|
359
|
+
// Initialize Graph client with client credentials auth provider (service account)
|
|
360
|
+
const authProvider = new ClientCredentialsAuthProvider(clientId, clientSecret, tenantId);
|
|
361
|
+
this._client = microsoft_graph_client_1.Client.initWithMiddleware({
|
|
362
|
+
authProvider: authProvider,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
// If credentials not available, initialize() must be called with OAuth config
|
|
222
366
|
}
|
|
223
367
|
/**
|
|
224
368
|
* Checks if SharePoint provider is properly configured.
|
|
225
|
-
* Returns true if
|
|
369
|
+
* Returns true if the Graph client is initialized and has required IDs.
|
|
226
370
|
*/
|
|
227
371
|
get IsConfigured() {
|
|
228
|
-
return !!(this.
|
|
372
|
+
return !!(this._client && this._driveId);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Initializes the SharePoint storage provider with OAuth configuration from the database.
|
|
376
|
+
* This method is called by the FileStorageProviderEngine when loading per-user provider configurations.
|
|
377
|
+
*
|
|
378
|
+
* For per-user OAuth, this sets up the RefreshTokenAuthProvider which uses the user's
|
|
379
|
+
* refresh token to obtain access tokens for their OneDrive/SharePoint files.
|
|
380
|
+
*
|
|
381
|
+
* @param config - Configuration object containing OAuth2 credentials
|
|
382
|
+
*/
|
|
383
|
+
async initialize(config) {
|
|
384
|
+
// Always call super to store accountId and accountName
|
|
385
|
+
await super.initialize(config);
|
|
386
|
+
if (!config) {
|
|
387
|
+
return; // Nothing to do, constructor already handled config from env/file
|
|
388
|
+
}
|
|
389
|
+
console.log('[SharePoint] Initializing with OAuth config...');
|
|
390
|
+
// Update OAuth2 credentials
|
|
391
|
+
this._clientID = config.clientID || this._clientID;
|
|
392
|
+
this._clientSecret = config.clientSecret || this._clientSecret;
|
|
393
|
+
this._refreshToken = config.refreshToken || this._refreshToken;
|
|
394
|
+
this._tenantID = config.tenantID || this._tenantID || 'common';
|
|
395
|
+
this._onTokenRefresh = config.onTokenRefresh;
|
|
396
|
+
// Update site/drive IDs if provided
|
|
397
|
+
if (config.siteID) {
|
|
398
|
+
this._siteId = config.siteID;
|
|
399
|
+
}
|
|
400
|
+
if (config.driveID) {
|
|
401
|
+
this._driveId = config.driveID;
|
|
402
|
+
}
|
|
403
|
+
if (config.rootFolderID) {
|
|
404
|
+
this._rootFolderId = config.rootFolderID;
|
|
405
|
+
}
|
|
406
|
+
// Validate we have required OAuth credentials
|
|
407
|
+
if (!this._clientID || !this._clientSecret || !this._refreshToken) {
|
|
408
|
+
throw new Error('SharePoint OAuth requires clientID, clientSecret, and refreshToken');
|
|
409
|
+
}
|
|
410
|
+
// Initialize the Graph client with refresh token auth provider (per-user OAuth)
|
|
411
|
+
const authProvider = new RefreshTokenAuthProvider(this._clientID, this._clientSecret, this._refreshToken, this._tenantID, this._onTokenRefresh);
|
|
412
|
+
this._client = microsoft_graph_client_1.Client.initWithMiddleware({
|
|
413
|
+
authProvider: authProvider,
|
|
414
|
+
});
|
|
415
|
+
// If no driveID provided, get the user's default OneDrive
|
|
416
|
+
if (!this._driveId) {
|
|
417
|
+
console.log("[SharePoint] No driveID provided, getting user's OneDrive...");
|
|
418
|
+
try {
|
|
419
|
+
const driveResponse = await this._client.api('/me/drive').get();
|
|
420
|
+
this._driveId = driveResponse.id;
|
|
421
|
+
console.log("[SharePoint] Using user's OneDrive:", this._driveId);
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
console.error("[SharePoint] Failed to get user's OneDrive:", error);
|
|
425
|
+
throw new Error("Failed to get user's OneDrive. Ensure the refresh token has Files.ReadWrite.All scope.");
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
console.log('[SharePoint] Initialized successfully with driveId:', this._driveId);
|
|
229
429
|
}
|
|
230
430
|
/**
|
|
231
431
|
* Gets the SharePoint item ID for a folder at the specified path
|
|
@@ -242,11 +442,12 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
242
442
|
if (!path || path === '/' || path === '') {
|
|
243
443
|
return this._rootFolderId || 'root';
|
|
244
444
|
}
|
|
245
|
-
const pathParts = path.split('/').filter(p => p);
|
|
445
|
+
const pathParts = path.split('/').filter((p) => p);
|
|
246
446
|
let currentFolderId = this._rootFolderId || 'root';
|
|
247
447
|
for (let i = 0; i < pathParts.length; i++) {
|
|
248
448
|
const folderName = pathParts[i];
|
|
249
|
-
const result = await this._client
|
|
449
|
+
const result = await this._client
|
|
450
|
+
.api(`/drives/${this._driveId}/items/${currentFolderId}/children`)
|
|
250
451
|
.filter(`name eq '${folderName}' and folder ne null`)
|
|
251
452
|
.get();
|
|
252
453
|
if (!result.value || result.value.length === 0) {
|
|
@@ -276,9 +477,7 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
276
477
|
const normalizedPath = path.startsWith('/') ? path.substring(1) : path;
|
|
277
478
|
try {
|
|
278
479
|
// Try to get the item directly by path
|
|
279
|
-
const driveRoot = this._rootFolderId ?
|
|
280
|
-
`/drives/${this._driveId}/items/${this._rootFolderId}` :
|
|
281
|
-
`/drives/${this._driveId}/root`;
|
|
480
|
+
const driveRoot = this._rootFolderId ? `/drives/${this._driveId}/items/${this._rootFolderId}` : `/drives/${this._driveId}/root`;
|
|
282
481
|
return await this._client.api(`${driveRoot}:/${normalizedPath}`).get();
|
|
283
482
|
}
|
|
284
483
|
catch (error) {
|
|
@@ -317,7 +516,7 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
317
516
|
lastModified: new Date(item.lastModifiedDateTime),
|
|
318
517
|
isDirectory,
|
|
319
518
|
etag: item.eTag,
|
|
320
|
-
customMetadata: {}
|
|
519
|
+
customMetadata: {},
|
|
321
520
|
};
|
|
322
521
|
}
|
|
323
522
|
/**
|
|
@@ -372,11 +571,10 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
372
571
|
try {
|
|
373
572
|
const item = await this._getItemByPath(objectName);
|
|
374
573
|
// Request a download URL - this is a time-limited URL
|
|
375
|
-
const downloadUrl = await this._client.api(`/drives/${this._driveId}/items/${item.id}/createLink`)
|
|
376
|
-
.post({
|
|
574
|
+
const downloadUrl = await this._client.api(`/drives/${this._driveId}/items/${item.id}/createLink`).post({
|
|
377
575
|
type: 'view',
|
|
378
576
|
scope: 'anonymous',
|
|
379
|
-
expirationDateTime: new Date(Date.now() + 10 * 60 * 1000).toISOString() // 10 minutes
|
|
577
|
+
expirationDateTime: new Date(Date.now() + 10 * 60 * 1000).toISOString(), // 10 minutes
|
|
380
578
|
});
|
|
381
579
|
return downloadUrl.link.webUrl;
|
|
382
580
|
}
|
|
@@ -421,12 +619,11 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
421
619
|
// Get the new parent folder ID
|
|
422
620
|
const parentFolderId = await this._getParentFolderIdByPath(newParentPath);
|
|
423
621
|
// Move the item
|
|
424
|
-
await this._client.api(`/drives/${this._driveId}/items/${item.id}`)
|
|
425
|
-
.update({
|
|
622
|
+
await this._client.api(`/drives/${this._driveId}/items/${item.id}`).update({
|
|
426
623
|
name: newName,
|
|
427
624
|
parentReference: {
|
|
428
|
-
id: parentFolderId
|
|
429
|
-
}
|
|
625
|
+
id: parentFolderId,
|
|
626
|
+
},
|
|
430
627
|
});
|
|
431
628
|
return true;
|
|
432
629
|
}
|
|
@@ -465,8 +662,7 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
465
662
|
try {
|
|
466
663
|
const item = await this._getItemByPath(objectName);
|
|
467
664
|
// Delete the item
|
|
468
|
-
await this._client.api(`/drives/${this._driveId}/items/${item.id}`)
|
|
469
|
-
.delete();
|
|
665
|
+
await this._client.api(`/drives/${this._driveId}/items/${item.id}`).delete();
|
|
470
666
|
return true;
|
|
471
667
|
}
|
|
472
668
|
catch (error) {
|
|
@@ -520,9 +716,7 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
520
716
|
for (const item of children.value) {
|
|
521
717
|
if (item.folder) {
|
|
522
718
|
// This is a folder/directory
|
|
523
|
-
const folderPath = prefix ?
|
|
524
|
-
(prefix.endsWith('/') ? `${prefix}${item.name}` : `${prefix}/${item.name}`) :
|
|
525
|
-
item.name;
|
|
719
|
+
const folderPath = prefix ? (prefix.endsWith('/') ? `${prefix}${item.name}` : `${prefix}/${item.name}`) : item.name;
|
|
526
720
|
prefixes.push(`${folderPath}/`);
|
|
527
721
|
}
|
|
528
722
|
// Add all items as objects (including folders)
|
|
@@ -571,9 +765,7 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
571
765
|
async CreateDirectory(directoryPath) {
|
|
572
766
|
try {
|
|
573
767
|
// Remove trailing slash if present
|
|
574
|
-
const normalizedPath = directoryPath.endsWith('/') ?
|
|
575
|
-
directoryPath.substring(0, directoryPath.length - 1) :
|
|
576
|
-
directoryPath;
|
|
768
|
+
const normalizedPath = directoryPath.endsWith('/') ? directoryPath.substring(0, directoryPath.length - 1) : directoryPath;
|
|
577
769
|
// Parse the path to get the parent folder and new folder name
|
|
578
770
|
const pathParts = normalizedPath.split('/');
|
|
579
771
|
const folderName = pathParts.pop() || '';
|
|
@@ -581,11 +773,10 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
581
773
|
// Get the parent folder ID
|
|
582
774
|
const parentFolderId = await this._getParentFolderIdByPath(parentPath);
|
|
583
775
|
// Create the folder
|
|
584
|
-
await this._client.api(`/drives/${this._driveId}/items/${parentFolderId}/children`)
|
|
585
|
-
.post({
|
|
776
|
+
await this._client.api(`/drives/${this._driveId}/items/${parentFolderId}/children`).post({
|
|
586
777
|
name: folderName,
|
|
587
778
|
folder: {},
|
|
588
|
-
'@microsoft.graph.conflictBehavior': 'fail'
|
|
779
|
+
'@microsoft.graph.conflictBehavior': 'fail',
|
|
589
780
|
});
|
|
590
781
|
return true;
|
|
591
782
|
}
|
|
@@ -627,9 +818,7 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
627
818
|
async DeleteDirectory(directoryPath, recursive = false) {
|
|
628
819
|
try {
|
|
629
820
|
// Remove trailing slash if present
|
|
630
|
-
const normalizedPath = directoryPath.endsWith('/') ?
|
|
631
|
-
directoryPath.substring(0, directoryPath.length - 1) :
|
|
632
|
-
directoryPath;
|
|
821
|
+
const normalizedPath = directoryPath.endsWith('/') ? directoryPath.substring(0, directoryPath.length - 1) : directoryPath;
|
|
633
822
|
const folder = await this._getItemByPath(normalizedPath);
|
|
634
823
|
if (!recursive) {
|
|
635
824
|
// Check if folder is empty
|
|
@@ -639,8 +828,7 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
639
828
|
}
|
|
640
829
|
}
|
|
641
830
|
// Delete the folder (SharePoint will delete recursively by default)
|
|
642
|
-
await this._client.api(`/drives/${this._driveId}/items/${folder.id}`)
|
|
643
|
-
.delete();
|
|
831
|
+
await this._client.api(`/drives/${this._driveId}/items/${folder.id}`).delete();
|
|
644
832
|
return true;
|
|
645
833
|
}
|
|
646
834
|
catch (error) {
|
|
@@ -821,17 +1009,15 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
821
1009
|
const effectiveContentType = contentType || mime.lookup(objectName) || 'application/octet-stream';
|
|
822
1010
|
if (data.length < 4 * 1024 * 1024) {
|
|
823
1011
|
// For small files (< 4MB), use simple upload
|
|
824
|
-
await this._client.api(`/drives/${this._driveId}/items/${parentFolderId}:/${fileName}:/content`)
|
|
825
|
-
.put(data);
|
|
1012
|
+
await this._client.api(`/drives/${this._driveId}/items/${parentFolderId}:/${fileName}:/content`).put(data);
|
|
826
1013
|
}
|
|
827
1014
|
else {
|
|
828
1015
|
// For larger files, use upload session
|
|
829
1016
|
// Create upload session
|
|
830
|
-
const uploadSession = await this._client.api(`/drives/${this._driveId}/items/${parentFolderId}:/${fileName}:/createUploadSession`)
|
|
831
|
-
.post({
|
|
1017
|
+
const uploadSession = await this._client.api(`/drives/${this._driveId}/items/${parentFolderId}:/${fileName}:/createUploadSession`).post({
|
|
832
1018
|
item: {
|
|
833
|
-
'@microsoft.graph.conflictBehavior': 'replace'
|
|
834
|
-
}
|
|
1019
|
+
'@microsoft.graph.conflictBehavior': 'replace',
|
|
1020
|
+
},
|
|
835
1021
|
});
|
|
836
1022
|
// Upload the file in chunks (could be improved with parallel uploads)
|
|
837
1023
|
const maxChunkSize = 60 * 1024 * 1024; // 60 MB chunks
|
|
@@ -842,9 +1028,9 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
842
1028
|
method: 'PUT',
|
|
843
1029
|
headers: {
|
|
844
1030
|
'Content-Length': chunk.length.toString(),
|
|
845
|
-
'Content-Range': contentRange
|
|
1031
|
+
'Content-Range': contentRange,
|
|
846
1032
|
},
|
|
847
|
-
body: chunk
|
|
1033
|
+
body: chunk,
|
|
848
1034
|
});
|
|
849
1035
|
}
|
|
850
1036
|
}
|
|
@@ -896,12 +1082,11 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
896
1082
|
// Get destination parent folder ID
|
|
897
1083
|
const destParentId = await this._getParentFolderIdByPath(destParentPath);
|
|
898
1084
|
// Create a copy
|
|
899
|
-
await this._client.api(`/drives/${this._driveId}/items/${sourceItem.id}/copy`)
|
|
900
|
-
.post({
|
|
1085
|
+
await this._client.api(`/drives/${this._driveId}/items/${sourceItem.id}/copy`).post({
|
|
901
1086
|
parentReference: {
|
|
902
|
-
id: destParentId
|
|
1087
|
+
id: destParentId,
|
|
903
1088
|
},
|
|
904
|
-
name: destName
|
|
1089
|
+
name: destName,
|
|
905
1090
|
});
|
|
906
1091
|
return true;
|
|
907
1092
|
}
|
|
@@ -975,9 +1160,7 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
975
1160
|
async DirectoryExists(directoryPath) {
|
|
976
1161
|
try {
|
|
977
1162
|
// Remove trailing slash if present
|
|
978
|
-
const normalizedPath = directoryPath.endsWith('/') ?
|
|
979
|
-
directoryPath.substring(0, directoryPath.length - 1) :
|
|
980
|
-
directoryPath;
|
|
1163
|
+
const normalizedPath = directoryPath.endsWith('/') ? directoryPath.substring(0, directoryPath.length - 1) : directoryPath;
|
|
981
1164
|
const item = await this._getItemByPath(normalizedPath);
|
|
982
1165
|
return !!item.folder;
|
|
983
1166
|
}
|
|
@@ -1061,26 +1244,16 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
1061
1244
|
{
|
|
1062
1245
|
entityTypes: ['driveItem'],
|
|
1063
1246
|
query: {
|
|
1064
|
-
queryString: kqlQuery
|
|
1247
|
+
queryString: kqlQuery,
|
|
1065
1248
|
},
|
|
1066
1249
|
from: 0,
|
|
1067
1250
|
size: maxResults,
|
|
1068
|
-
fields: [
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
'size',
|
|
1072
|
-
'lastModifiedDateTime',
|
|
1073
|
-
'fileSystemInfo',
|
|
1074
|
-
'webUrl',
|
|
1075
|
-
'id',
|
|
1076
|
-
'contentType'
|
|
1077
|
-
]
|
|
1078
|
-
}
|
|
1079
|
-
]
|
|
1251
|
+
fields: ['name', 'path', 'size', 'lastModifiedDateTime', 'fileSystemInfo', 'webUrl', 'id', 'contentType'],
|
|
1252
|
+
},
|
|
1253
|
+
],
|
|
1080
1254
|
};
|
|
1081
1255
|
// Execute the search
|
|
1082
|
-
const response = await this._client.api('/search/query')
|
|
1083
|
-
.post(searchRequest);
|
|
1256
|
+
const response = await this._client.api('/search/query').post(searchRequest);
|
|
1084
1257
|
// Transform results
|
|
1085
1258
|
return this.transformSearchResults(response, maxResults);
|
|
1086
1259
|
}
|
|
@@ -1112,14 +1285,13 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
1112
1285
|
queryParts.push(`(Path:"${this._siteId}/${this._driveId}")`);
|
|
1113
1286
|
// Add path prefix filter if specified
|
|
1114
1287
|
if (options?.pathPrefix) {
|
|
1115
|
-
const normalizedPrefix = options.pathPrefix.startsWith('/')
|
|
1116
|
-
? options.pathPrefix.substring(1)
|
|
1117
|
-
: options.pathPrefix;
|
|
1288
|
+
const normalizedPrefix = options.pathPrefix.startsWith('/') ? options.pathPrefix.substring(1) : options.pathPrefix;
|
|
1118
1289
|
queryParts.push(`(Path:"${this._siteId}/${this._driveId}/${normalizedPrefix}*")`);
|
|
1119
1290
|
}
|
|
1120
1291
|
// Add file type filters if specified
|
|
1121
1292
|
if (options?.fileTypes && options.fileTypes.length > 0) {
|
|
1122
|
-
const fileTypeFilters = options.fileTypes
|
|
1293
|
+
const fileTypeFilters = options.fileTypes
|
|
1294
|
+
.map((fileType) => {
|
|
1123
1295
|
// Handle both extensions (pdf) and MIME types (application/pdf)
|
|
1124
1296
|
if (fileType.includes('/')) {
|
|
1125
1297
|
// It's a MIME type
|
|
@@ -1130,7 +1302,8 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
1130
1302
|
const extension = fileType.startsWith('.') ? fileType.substring(1) : fileType;
|
|
1131
1303
|
return `FileType:${extension}`;
|
|
1132
1304
|
}
|
|
1133
|
-
})
|
|
1305
|
+
})
|
|
1306
|
+
.join(' OR ');
|
|
1134
1307
|
queryParts.push(`(${fileTypeFilters})`);
|
|
1135
1308
|
}
|
|
1136
1309
|
// Add date filters if specified
|
|
@@ -1187,19 +1360,17 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
1187
1360
|
name: resource.name || '',
|
|
1188
1361
|
size: resource.size || 0,
|
|
1189
1362
|
contentType: resource.file?.mimeType || mime.lookup(resource.name) || 'application/octet-stream',
|
|
1190
|
-
lastModified: resource.lastModifiedDateTime
|
|
1191
|
-
? new Date(resource.lastModifiedDateTime)
|
|
1192
|
-
: new Date(),
|
|
1363
|
+
lastModified: resource.lastModifiedDateTime ? new Date(resource.lastModifiedDateTime) : new Date(),
|
|
1193
1364
|
objectId: resource.id || '', // SharePoint item ID for direct access
|
|
1194
|
-
relevance: hit.rank ?
|
|
1365
|
+
relevance: hit.rank ? hit.rank / 100.0 : undefined,
|
|
1195
1366
|
excerpt: hit.summary || undefined,
|
|
1196
1367
|
matchInFilename: this.determineMatchLocation(hit),
|
|
1197
1368
|
providerData: {
|
|
1198
1369
|
id: resource.id,
|
|
1199
1370
|
webUrl: resource.webUrl,
|
|
1200
1371
|
driveId: this._driveId,
|
|
1201
|
-
siteId: this._siteId
|
|
1202
|
-
}
|
|
1372
|
+
siteId: this._siteId,
|
|
1373
|
+
},
|
|
1203
1374
|
};
|
|
1204
1375
|
results.push(result);
|
|
1205
1376
|
}
|
|
@@ -1209,7 +1380,7 @@ let SharePointFileStorage = class SharePointFileStorage extends FileStorageBase_
|
|
|
1209
1380
|
return {
|
|
1210
1381
|
results,
|
|
1211
1382
|
totalMatches,
|
|
1212
|
-
hasMore
|
|
1383
|
+
hasMore,
|
|
1213
1384
|
};
|
|
1214
1385
|
}
|
|
1215
1386
|
/**
|