@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
package/dist/util.js
CHANGED
|
@@ -3,10 +3,193 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.deleteObject = exports.moveObject = exports.createDownloadUrl = exports.createUploadUrl = void 0;
|
|
6
|
+
exports.searchAcrossAccounts = exports.searchAcrossProviders = exports.copyObjectBetweenProviders = exports.listObjects = exports.deleteObject = exports.copyObject = exports.moveObject = exports.createDownloadUrl = exports.createUploadUrl = exports.initializeDriverWithAccountCredentials = exports.initializeDriverWithUserCredentials = void 0;
|
|
7
7
|
const global_1 = require("@memberjunction/global");
|
|
8
|
+
const core_1 = require("@memberjunction/core");
|
|
9
|
+
const credentials_1 = require("@memberjunction/credentials");
|
|
8
10
|
const mime_types_1 = __importDefault(require("mime-types"));
|
|
9
11
|
const FileStorageBase_1 = require("./generic/FileStorageBase");
|
|
12
|
+
/**
|
|
13
|
+
* @deprecated This function is being replaced by the enterprise file storage model.
|
|
14
|
+
* Use FileStorageAccount with Credential Engine instead.
|
|
15
|
+
*
|
|
16
|
+
* Initializes a storage driver with user-specific OAuth credentials.
|
|
17
|
+
* NOTE: This function currently only supports non-OAuth providers.
|
|
18
|
+
* OAuth provider support will be added via the Credential Engine integration.
|
|
19
|
+
*
|
|
20
|
+
* @param options - Configuration options including provider, user ID, and context
|
|
21
|
+
* @returns A promise resolving to an initialized FileStorageBase driver
|
|
22
|
+
* @throws Error if the provider requires OAuth (not yet supported in enterprise model)
|
|
23
|
+
*/
|
|
24
|
+
async function initializeDriverWithUserCredentials(options) {
|
|
25
|
+
const { providerEntity } = options;
|
|
26
|
+
// Create the driver instance
|
|
27
|
+
const driver = global_1.MJGlobal.Instance.ClassFactory.CreateInstance(FileStorageBase_1.FileStorageBase, providerEntity.ServerDriverKey);
|
|
28
|
+
// Check if this provider requires OAuth authentication
|
|
29
|
+
if (providerEntity.RequiresOAuth) {
|
|
30
|
+
// TODO: Implement Credential Engine integration for OAuth providers
|
|
31
|
+
// This will load credentials from FileStorageAccount -> Credential
|
|
32
|
+
throw new Error(`Provider "${providerEntity.Name}" requires OAuth authentication. ` + `Enterprise OAuth support via Credential Engine is not yet implemented.`);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// Provider doesn't require OAuth - use admin/global configuration
|
|
36
|
+
const configJson = providerEntity.Configuration;
|
|
37
|
+
if (configJson) {
|
|
38
|
+
const config = JSON.parse(configJson);
|
|
39
|
+
await driver.initialize(config);
|
|
40
|
+
console.log(`[initializeDriverWithUserCredentials] Initialized ${providerEntity.Name} with admin configuration`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return driver;
|
|
44
|
+
}
|
|
45
|
+
exports.initializeDriverWithUserCredentials = initializeDriverWithUserCredentials;
|
|
46
|
+
/**
|
|
47
|
+
* Initializes a storage driver using account-based credentials from the Credential Engine.
|
|
48
|
+
* This is the enterprise model where credentials are stored in the Credential entity
|
|
49
|
+
* and decrypted at runtime.
|
|
50
|
+
*
|
|
51
|
+
* For providers that issue new refresh tokens on each token refresh (like Box.com),
|
|
52
|
+
* this function automatically configures a callback to persist the new tokens back
|
|
53
|
+
* to the Credential entity in the database.
|
|
54
|
+
*
|
|
55
|
+
* @param options - Configuration options including account, provider, and context user
|
|
56
|
+
* @returns A promise resolving to an initialized FileStorageBase driver
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* const driver = await initializeDriverWithAccountCredentials({
|
|
61
|
+
* accountEntity,
|
|
62
|
+
* providerEntity,
|
|
63
|
+
* contextUser
|
|
64
|
+
* });
|
|
65
|
+
*
|
|
66
|
+
* // Driver is now ready to use
|
|
67
|
+
* const objects = await driver.ListObjects('/');
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
async function initializeDriverWithAccountCredentials(options) {
|
|
71
|
+
const { accountEntity, providerEntity, contextUser } = options;
|
|
72
|
+
// Create the driver instance using the provider's driver key
|
|
73
|
+
const driver = global_1.MJGlobal.Instance.ClassFactory.CreateInstance(FileStorageBase_1.FileStorageBase, providerEntity.ServerDriverKey);
|
|
74
|
+
if (!driver) {
|
|
75
|
+
throw new Error(`Failed to create storage driver for provider "${providerEntity.Name}" ` + `with driver key "${providerEntity.ServerDriverKey}"`);
|
|
76
|
+
}
|
|
77
|
+
// Build the base config with account information (required in enterprise model)
|
|
78
|
+
const baseConfig = {
|
|
79
|
+
accountId: accountEntity.ID,
|
|
80
|
+
accountName: accountEntity.Name,
|
|
81
|
+
};
|
|
82
|
+
// Check if the account has a credential configured
|
|
83
|
+
if (accountEntity.CredentialID) {
|
|
84
|
+
// Initialize the Credential Engine if not already done
|
|
85
|
+
await credentials_1.CredentialEngine.Instance.Config(false, contextUser);
|
|
86
|
+
// Get the credential by ID and decrypt it
|
|
87
|
+
const credentialEntity = credentials_1.CredentialEngine.Instance.getCredentialById(accountEntity.CredentialID);
|
|
88
|
+
if (!credentialEntity) {
|
|
89
|
+
throw new Error(`Credential with ID ${accountEntity.CredentialID} not found for account "${accountEntity.Name}"`);
|
|
90
|
+
}
|
|
91
|
+
// Resolve the credential to get decrypted values
|
|
92
|
+
const resolved = await credentials_1.CredentialEngine.Instance.getCredential(credentialEntity.Name, {
|
|
93
|
+
credentialId: accountEntity.CredentialID,
|
|
94
|
+
contextUser,
|
|
95
|
+
subsystem: 'FileStorage',
|
|
96
|
+
});
|
|
97
|
+
(0, core_1.LogStatus)(`[initializeDriverWithAccountCredentials] Decrypted credential "${credentialEntity.Name}" for account "${accountEntity.Name}"`);
|
|
98
|
+
// Create a token refresh callback to persist new tokens back to the database
|
|
99
|
+
// This is critical for providers like Box.com that issue new refresh tokens on each refresh
|
|
100
|
+
const onTokenRefresh = async (newRefreshToken, newAccessToken) => {
|
|
101
|
+
try {
|
|
102
|
+
(0, core_1.LogStatus)(`[initializeDriverWithAccountCredentials] Token refresh callback invoked for account "${accountEntity.Name}"`);
|
|
103
|
+
// Get the current credential values and update with new tokens
|
|
104
|
+
const updatedValues = { ...resolved.values };
|
|
105
|
+
updatedValues.refreshToken = newRefreshToken;
|
|
106
|
+
if (newAccessToken) {
|
|
107
|
+
updatedValues.accessToken = newAccessToken;
|
|
108
|
+
}
|
|
109
|
+
// Update the credential in the database using the Credential Engine
|
|
110
|
+
await credentials_1.CredentialEngine.Instance.updateCredential(accountEntity.CredentialID, updatedValues, contextUser);
|
|
111
|
+
(0, core_1.LogStatus)(`[initializeDriverWithAccountCredentials] Successfully persisted new tokens for account "${accountEntity.Name}"`);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
// Log the error but don't throw - the driver still has the tokens in memory
|
|
115
|
+
// and can continue operating. However, the next server restart will fail.
|
|
116
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
117
|
+
console.error(`[initializeDriverWithAccountCredentials] Failed to persist new tokens for account "${accountEntity.Name}": ${errorMessage}`);
|
|
118
|
+
console.error(`[initializeDriverWithAccountCredentials] WARNING: Authentication may fail after server restart!`);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
// Add the token refresh callback to the config
|
|
122
|
+
baseConfig.onTokenRefresh = onTokenRefresh;
|
|
123
|
+
// Initialize the driver with account info + decrypted credential values + token refresh callback
|
|
124
|
+
await driver.initialize({
|
|
125
|
+
...baseConfig,
|
|
126
|
+
...resolved.values,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
// No credential configured - fall back to provider's static configuration
|
|
131
|
+
const configJson = providerEntity.Configuration;
|
|
132
|
+
if (configJson) {
|
|
133
|
+
const config = JSON.parse(configJson);
|
|
134
|
+
// Merge account info with provider configuration
|
|
135
|
+
await driver.initialize({
|
|
136
|
+
...baseConfig,
|
|
137
|
+
...config,
|
|
138
|
+
});
|
|
139
|
+
(0, core_1.LogStatus)(`[initializeDriverWithAccountCredentials] Initialized "${accountEntity.Name}" with provider configuration (no credential)`);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
// Initialize with just account info (driver may use env vars or other config)
|
|
143
|
+
await driver.initialize(baseConfig);
|
|
144
|
+
(0, core_1.LogStatus)(`[initializeDriverWithAccountCredentials] Warning: No credential or configuration for account "${accountEntity.Name}"`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return driver;
|
|
148
|
+
}
|
|
149
|
+
exports.initializeDriverWithAccountCredentials = initializeDriverWithAccountCredentials;
|
|
150
|
+
/**
|
|
151
|
+
* Internal helper to initialize a storage driver with appropriate credentials.
|
|
152
|
+
*
|
|
153
|
+
* Supports two modes:
|
|
154
|
+
* 1. Enterprise model (preferred): When accountEntity is provided in userContext,
|
|
155
|
+
* uses the Credential Engine to decrypt credentials from the account's CredentialID.
|
|
156
|
+
* 2. Legacy model: Uses provider configuration or throws for OAuth providers.
|
|
157
|
+
*
|
|
158
|
+
* @param providerEntity - The file storage provider entity
|
|
159
|
+
* @param userContext - Optional user context, may include accountEntity for enterprise model
|
|
160
|
+
* @returns An initialized FileStorageBase driver
|
|
161
|
+
*/
|
|
162
|
+
async function initializeDriver(providerEntity, userContext) {
|
|
163
|
+
// Enterprise model: Use account-based credentials if accountEntity is provided
|
|
164
|
+
if (userContext?.accountEntity) {
|
|
165
|
+
return initializeDriverWithAccountCredentials({
|
|
166
|
+
accountEntity: userContext.accountEntity,
|
|
167
|
+
providerEntity,
|
|
168
|
+
contextUser: userContext.contextUser,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
// Legacy model: Use the deprecated user credentials approach
|
|
172
|
+
if (userContext) {
|
|
173
|
+
return initializeDriverWithUserCredentials({
|
|
174
|
+
providerEntity,
|
|
175
|
+
userID: userContext.userID,
|
|
176
|
+
contextUser: userContext.contextUser,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
// No user context - use admin/legacy initialization
|
|
180
|
+
const driver = global_1.MJGlobal.Instance.ClassFactory.CreateInstance(FileStorageBase_1.FileStorageBase, providerEntity.ServerDriverKey);
|
|
181
|
+
// Check if this provider requires OAuth but no user context was provided
|
|
182
|
+
if (providerEntity.RequiresOAuth) {
|
|
183
|
+
throw new Error(`Provider "${providerEntity.Name}" requires OAuth authentication. ` + `Please provide userContext parameter with userID and contextUser.`);
|
|
184
|
+
}
|
|
185
|
+
// Initialize with admin configuration
|
|
186
|
+
const configJson = providerEntity.Get('Configuration');
|
|
187
|
+
if (configJson) {
|
|
188
|
+
const config = JSON.parse(configJson);
|
|
189
|
+
await driver.initialize(config);
|
|
190
|
+
}
|
|
191
|
+
return driver;
|
|
192
|
+
}
|
|
10
193
|
/**
|
|
11
194
|
* Creates a pre-authenticated upload URL for a file in the specified file storage provider.
|
|
12
195
|
*
|
|
@@ -27,6 +210,7 @@ const FileStorageBase_1 = require("./generic/FileStorageBase");
|
|
|
27
210
|
* - ProviderID: The ID of the storage provider to use
|
|
28
211
|
* - ContentType: (Optional) The MIME type of the file
|
|
29
212
|
* - ProviderKey: (Optional) Provider-specific key for the file
|
|
213
|
+
* @param userContext - Optional user context for OAuth providers (required if provider.RequiresOAuth is true)
|
|
30
214
|
* @returns A promise that resolves to an object containing:
|
|
31
215
|
* - updatedInput: The input object with additional metadata (Status, ContentType, and possibly ProviderKey)
|
|
32
216
|
* - UploadUrl: The pre-authenticated URL for uploading the file
|
|
@@ -41,7 +225,8 @@ const FileStorageBase_1 = require("./generic/FileStorageBase");
|
|
|
41
225
|
* ID: '123',
|
|
42
226
|
* Name: 'report.pdf',
|
|
43
227
|
* ProviderID: fileStorageProvider.ID
|
|
44
|
-
* }
|
|
228
|
+
* },
|
|
229
|
+
* { userID: currentUser.ID, contextUser } // Required for OAuth providers
|
|
45
230
|
* );
|
|
46
231
|
*
|
|
47
232
|
* // The content type is automatically determined from the file extension
|
|
@@ -54,12 +239,13 @@ const FileStorageBase_1 = require("./generic/FileStorageBase");
|
|
|
54
239
|
* console.log(result.UploadUrl);
|
|
55
240
|
* ```
|
|
56
241
|
*/
|
|
57
|
-
const createUploadUrl = async (providerEntity, input) => {
|
|
242
|
+
const createUploadUrl = async (providerEntity, input, userContext) => {
|
|
58
243
|
const { Name, ProviderID } = input;
|
|
59
244
|
const ContentType = input.ContentType ?? mime_types_1.default.lookup(input.Name) ?? 'application/octet-stream';
|
|
60
245
|
const Status = 'Uploading';
|
|
61
246
|
await providerEntity.Load(ProviderID);
|
|
62
|
-
|
|
247
|
+
// Initialize driver with user credentials if available, otherwise use admin config
|
|
248
|
+
const driver = await initializeDriver(providerEntity, userContext);
|
|
63
249
|
const { UploadUrl, ...maybeProviderKey } = await driver.CreatePreAuthUploadUrl(Name);
|
|
64
250
|
const updatedInput = { ...input, ...maybeProviderKey, ContentType, Status };
|
|
65
251
|
return { updatedInput, UploadUrl };
|
|
@@ -76,6 +262,7 @@ exports.createUploadUrl = createUploadUrl;
|
|
|
76
262
|
* @param providerEntity - The file storage provider entity containing connection details
|
|
77
263
|
* @param providerKeyOrName - The provider key or name of the file to download
|
|
78
264
|
* (use the ProviderKey if it was returned during upload, otherwise use the file Name)
|
|
265
|
+
* @param userContext - Optional user context for OAuth providers (required if provider.RequiresOAuth is true)
|
|
79
266
|
* @returns A promise that resolves to the pre-authenticated download URL as a string
|
|
80
267
|
*
|
|
81
268
|
* @example
|
|
@@ -84,17 +271,17 @@ exports.createUploadUrl = createUploadUrl;
|
|
|
84
271
|
* const fileStorageProvider = await entityMgr.FindById('FileStorageProvider', 'azure-main');
|
|
85
272
|
*
|
|
86
273
|
* // Using the file name
|
|
87
|
-
* const downloadUrl = await createDownloadUrl(fileStorageProvider, 'reports/annual-report.pdf');
|
|
274
|
+
* const downloadUrl = await createDownloadUrl(fileStorageProvider, 'reports/annual-report.pdf', userContext);
|
|
88
275
|
*
|
|
89
276
|
* // Or using the provider key if returned during upload
|
|
90
|
-
* const downloadUrl = await createDownloadUrl(fileStorageProvider, file.ProviderKey);
|
|
277
|
+
* const downloadUrl = await createDownloadUrl(fileStorageProvider, file.ProviderKey, userContext);
|
|
91
278
|
*
|
|
92
279
|
* // The download URL can be provided to clients for direct download
|
|
93
280
|
* console.log(downloadUrl);
|
|
94
281
|
* ```
|
|
95
282
|
*/
|
|
96
|
-
const createDownloadUrl = async (providerEntity, providerKeyOrName) => {
|
|
97
|
-
const driver =
|
|
283
|
+
const createDownloadUrl = async (providerEntity, providerKeyOrName, userContext) => {
|
|
284
|
+
const driver = await initializeDriver(providerEntity, userContext);
|
|
98
285
|
return driver.CreatePreAuthDownloadUrl(providerKeyOrName);
|
|
99
286
|
};
|
|
100
287
|
exports.createDownloadUrl = createDownloadUrl;
|
|
@@ -109,6 +296,7 @@ exports.createDownloadUrl = createDownloadUrl;
|
|
|
109
296
|
* @param oldProviderKeyOrName - The key or name of the object's current location
|
|
110
297
|
* (use the ProviderKey if it was returned during upload, otherwise use the file Name)
|
|
111
298
|
* @param newProviderKeyOrName - The key or name for the object's new location
|
|
299
|
+
* @param userContext - Optional user context for OAuth providers (required if provider.RequiresOAuth is true)
|
|
112
300
|
* @returns A promise that resolves to a boolean indicating whether the move operation was successful
|
|
113
301
|
*
|
|
114
302
|
* @example
|
|
@@ -120,7 +308,8 @@ exports.createDownloadUrl = createDownloadUrl;
|
|
|
120
308
|
* const success = await moveObject(
|
|
121
309
|
* fileStorageProvider,
|
|
122
310
|
* 'drafts/report.docx',
|
|
123
|
-
* 'published/final-report.docx'
|
|
311
|
+
* 'published/final-report.docx',
|
|
312
|
+
* userContext
|
|
124
313
|
* );
|
|
125
314
|
*
|
|
126
315
|
* if (success) {
|
|
@@ -130,11 +319,46 @@ exports.createDownloadUrl = createDownloadUrl;
|
|
|
130
319
|
* }
|
|
131
320
|
* ```
|
|
132
321
|
*/
|
|
133
|
-
const moveObject = async (providerEntity, oldProviderKeyOrName, newProviderKeyOrName) => {
|
|
134
|
-
const driver =
|
|
322
|
+
const moveObject = async (providerEntity, oldProviderKeyOrName, newProviderKeyOrName, userContext) => {
|
|
323
|
+
const driver = await initializeDriver(providerEntity, userContext);
|
|
135
324
|
return driver.MoveObject(oldProviderKeyOrName, newProviderKeyOrName);
|
|
136
325
|
};
|
|
137
326
|
exports.moveObject = moveObject;
|
|
327
|
+
/**
|
|
328
|
+
* Copies an object from one location to another within the specified file storage provider.
|
|
329
|
+
*
|
|
330
|
+
* This utility function handles copying files by instantiating the appropriate storage
|
|
331
|
+
* provider driver and delegating to its CopyObject method. It can be used to duplicate files
|
|
332
|
+
* within the same storage provider, either in the same folder with a different name or to
|
|
333
|
+
* a different folder.
|
|
334
|
+
*
|
|
335
|
+
* @param providerEntity - The file storage provider entity containing connection details
|
|
336
|
+
* @param sourceProviderKeyOrName - The key or name of the source file to copy
|
|
337
|
+
* @param destinationProviderKeyOrName - The key or name for the destination copy
|
|
338
|
+
* @param userContext - Optional user context for OAuth providers (required if provider.RequiresOAuth is true)
|
|
339
|
+
* @returns A promise that resolves to a boolean indicating whether the copy was successful
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
* ```typescript
|
|
343
|
+
* const success = await copyObject(
|
|
344
|
+
* providerEntity,
|
|
345
|
+
* 'documents/report.pdf',
|
|
346
|
+
* 'documents/archive/report-2024.pdf',
|
|
347
|
+
* userContext
|
|
348
|
+
* );
|
|
349
|
+
*
|
|
350
|
+
* if (success) {
|
|
351
|
+
* console.log('File successfully copied');
|
|
352
|
+
* } else {
|
|
353
|
+
* console.log('Failed to copy file');
|
|
354
|
+
* }
|
|
355
|
+
* ```
|
|
356
|
+
*/
|
|
357
|
+
const copyObject = async (providerEntity, sourceProviderKeyOrName, destinationProviderKeyOrName, userContext) => {
|
|
358
|
+
const driver = await initializeDriver(providerEntity, userContext);
|
|
359
|
+
return driver.CopyObject(sourceProviderKeyOrName, destinationProviderKeyOrName);
|
|
360
|
+
};
|
|
361
|
+
exports.copyObject = copyObject;
|
|
138
362
|
/**
|
|
139
363
|
* Deletes a file from the specified file storage provider.
|
|
140
364
|
*
|
|
@@ -145,6 +369,7 @@ exports.moveObject = moveObject;
|
|
|
145
369
|
* @param providerEntity - The file storage provider entity containing connection details
|
|
146
370
|
* @param providerKeyOrName - The key or name of the file to delete
|
|
147
371
|
* (use the ProviderKey if it was returned during upload, otherwise use the file Name)
|
|
372
|
+
* @param userContext - Optional user context for OAuth providers (required if provider.RequiresOAuth is true)
|
|
148
373
|
* @returns A promise that resolves to a boolean indicating whether the deletion was successful
|
|
149
374
|
*
|
|
150
375
|
* @example
|
|
@@ -153,10 +378,10 @@ exports.moveObject = moveObject;
|
|
|
153
378
|
* const fileStorageProvider = await entityMgr.FindById('FileStorageProvider', 'azure-main');
|
|
154
379
|
*
|
|
155
380
|
* // Delete using the file name
|
|
156
|
-
* const deleted = await deleteObject(fileStorageProvider, 'temp/obsolete-document.pdf');
|
|
381
|
+
* const deleted = await deleteObject(fileStorageProvider, 'temp/obsolete-document.pdf', userContext);
|
|
157
382
|
*
|
|
158
383
|
* // Or using the provider key if returned during upload
|
|
159
|
-
* const deleted = await deleteObject(fileStorageProvider, file.ProviderKey);
|
|
384
|
+
* const deleted = await deleteObject(fileStorageProvider, file.ProviderKey, userContext);
|
|
160
385
|
*
|
|
161
386
|
* if (deleted) {
|
|
162
387
|
* console.log('File successfully deleted');
|
|
@@ -165,9 +390,445 @@ exports.moveObject = moveObject;
|
|
|
165
390
|
* }
|
|
166
391
|
* ```
|
|
167
392
|
*/
|
|
168
|
-
const deleteObject = (providerEntity, providerKeyOrName) => {
|
|
169
|
-
|
|
170
|
-
|
|
393
|
+
const deleteObject = async (providerEntity, providerKeyOrName, userContext) => {
|
|
394
|
+
console.log('[deleteObject] Called with:', {
|
|
395
|
+
providerName: providerEntity.Name,
|
|
396
|
+
providerID: providerEntity.ID,
|
|
397
|
+
serverDriverKey: providerEntity.ServerDriverKey,
|
|
398
|
+
providerKeyOrName,
|
|
399
|
+
});
|
|
400
|
+
const driver = await initializeDriver(providerEntity, userContext);
|
|
401
|
+
console.log('[deleteObject] Driver initialized:', {
|
|
402
|
+
driverType: driver.constructor.name,
|
|
403
|
+
hasDeleteMethod: typeof driver.DeleteObject === 'function',
|
|
404
|
+
});
|
|
405
|
+
console.log('[deleteObject] Calling driver.DeleteObject...');
|
|
406
|
+
const result = await driver.DeleteObject(providerKeyOrName);
|
|
407
|
+
console.log('[deleteObject] Result:', result);
|
|
408
|
+
return result;
|
|
171
409
|
};
|
|
172
410
|
exports.deleteObject = deleteObject;
|
|
411
|
+
/**
|
|
412
|
+
* Lists objects (files) and prefixes (directories) in a storage provider at the specified path.
|
|
413
|
+
*
|
|
414
|
+
* This utility function provides access to the storage provider's file and folder listing
|
|
415
|
+
* functionality. It returns both files and directories found at the specified path prefix,
|
|
416
|
+
* allowing for hierarchical navigation through the storage provider's contents.
|
|
417
|
+
*
|
|
418
|
+
* @param providerEntity - The file storage provider entity containing connection details
|
|
419
|
+
* @param prefix - The path prefix to list objects from (e.g., "/" for root, "documents/" for a specific folder)
|
|
420
|
+
* @param delimiter - The character used to group keys into a hierarchy (defaults to "/")
|
|
421
|
+
* @param userContext - Optional user context for OAuth providers (required if provider.RequiresOAuth is true)
|
|
422
|
+
* @returns A promise that resolves to a StorageListResult containing:
|
|
423
|
+
* - objects: Array of file metadata (name, size, contentType, lastModified, etc.)
|
|
424
|
+
* - prefixes: Array of directory/folder path strings
|
|
425
|
+
*
|
|
426
|
+
* @example
|
|
427
|
+
* ```typescript
|
|
428
|
+
* // List contents of the root directory
|
|
429
|
+
* const fileStorageProvider = await entityMgr.FindById('FileStorageProvider', 'aws-s3-main');
|
|
430
|
+
* const result = await listObjects(fileStorageProvider, '/', '/', userContext);
|
|
431
|
+
*
|
|
432
|
+
* // Display files
|
|
433
|
+
* for (const file of result.objects) {
|
|
434
|
+
* console.log(`File: ${file.name} (${file.size} bytes)`);
|
|
435
|
+
* }
|
|
436
|
+
*
|
|
437
|
+
* // Display folders
|
|
438
|
+
* for (const folder of result.prefixes) {
|
|
439
|
+
* console.log(`Folder: ${folder}`);
|
|
440
|
+
* }
|
|
441
|
+
*
|
|
442
|
+
* // List contents of a specific folder
|
|
443
|
+
* const docsResult = await listObjects(fileStorageProvider, 'documents/', '/', userContext);
|
|
444
|
+
* ```
|
|
445
|
+
*/
|
|
446
|
+
const listObjects = async (providerEntity, prefix, delimiter = '/', userContext) => {
|
|
447
|
+
console.log('[listObjects] Starting with:', {
|
|
448
|
+
providerName: providerEntity.Name,
|
|
449
|
+
serverDriverKey: providerEntity.ServerDriverKey,
|
|
450
|
+
prefix,
|
|
451
|
+
delimiter,
|
|
452
|
+
hasUserContext: !!userContext,
|
|
453
|
+
});
|
|
454
|
+
const driver = await initializeDriver(providerEntity, userContext);
|
|
455
|
+
console.log('[listObjects] Driver initialized:', {
|
|
456
|
+
driverType: driver.constructor.name,
|
|
457
|
+
isConfigured: driver.IsConfigured,
|
|
458
|
+
});
|
|
459
|
+
const result = await driver.ListObjects(prefix, delimiter);
|
|
460
|
+
console.log('[listObjects] Result:', {
|
|
461
|
+
objectsCount: result.objects.length,
|
|
462
|
+
prefixesCount: result.prefixes.length,
|
|
463
|
+
});
|
|
464
|
+
return result;
|
|
465
|
+
};
|
|
466
|
+
exports.listObjects = listObjects;
|
|
467
|
+
/**
|
|
468
|
+
* Copies a file from one storage provider to another.
|
|
469
|
+
*
|
|
470
|
+
* This utility function enables transferring files between different storage providers
|
|
471
|
+
* (e.g., from Dropbox to Google Drive, or from S3 to Azure). The transfer happens
|
|
472
|
+
* server-side, so the file data flows: Source Provider → Server → Destination Provider.
|
|
473
|
+
*
|
|
474
|
+
* @param sourceProviderEntity - The source file storage provider entity
|
|
475
|
+
* @param destinationProviderEntity - The destination file storage provider entity
|
|
476
|
+
* @param sourcePath - The path to the file in the source provider
|
|
477
|
+
* @param destinationPath - The path where the file should be saved in the destination provider
|
|
478
|
+
* @param options - Optional user context for OAuth providers
|
|
479
|
+
* @returns A promise that resolves to a CopyBetweenProvidersResult
|
|
480
|
+
*
|
|
481
|
+
* @example
|
|
482
|
+
* ```typescript
|
|
483
|
+
* // Copy a file from Dropbox to Google Drive
|
|
484
|
+
* const sourceProvider = await entityMgr.FindById('FileStorageProvider', 'dropbox-id');
|
|
485
|
+
* const destProvider = await entityMgr.FindById('FileStorageProvider', 'gdrive-id');
|
|
486
|
+
*
|
|
487
|
+
* const result = await copyObjectBetweenProviders(
|
|
488
|
+
* sourceProvider,
|
|
489
|
+
* destProvider,
|
|
490
|
+
* 'documents/report.pdf',
|
|
491
|
+
* 'imported/report.pdf',
|
|
492
|
+
* {
|
|
493
|
+
* sourceUserContext: { userID: currentUser.ID, contextUser },
|
|
494
|
+
* destinationUserContext: { userID: currentUser.ID, contextUser }
|
|
495
|
+
* }
|
|
496
|
+
* );
|
|
497
|
+
*
|
|
498
|
+
* if (result.success) {
|
|
499
|
+
* console.log(`Transferred ${result.bytesTransferred} bytes`);
|
|
500
|
+
* }
|
|
501
|
+
* ```
|
|
502
|
+
*/
|
|
503
|
+
const copyObjectBetweenProviders = async (sourceProviderEntity, destinationProviderEntity, sourcePath, destinationPath, options) => {
|
|
504
|
+
console.log('[copyObjectBetweenProviders] Starting transfer:', {
|
|
505
|
+
sourceProvider: sourceProviderEntity.Name,
|
|
506
|
+
destinationProvider: destinationProviderEntity.Name,
|
|
507
|
+
sourcePath,
|
|
508
|
+
destinationPath,
|
|
509
|
+
});
|
|
510
|
+
const result = {
|
|
511
|
+
success: false,
|
|
512
|
+
message: '',
|
|
513
|
+
sourceProvider: sourceProviderEntity.Name,
|
|
514
|
+
destinationProvider: destinationProviderEntity.Name,
|
|
515
|
+
sourcePath,
|
|
516
|
+
destinationPath,
|
|
517
|
+
};
|
|
518
|
+
try {
|
|
519
|
+
// Initialize source driver with user credentials if available
|
|
520
|
+
const sourceDriver = await initializeDriver(sourceProviderEntity, options?.sourceUserContext);
|
|
521
|
+
// Initialize destination driver with user credentials if available
|
|
522
|
+
const destDriver = await initializeDriver(destinationProviderEntity, options?.destinationUserContext);
|
|
523
|
+
console.log('[copyObjectBetweenProviders] Drivers initialized, fetching file from source...');
|
|
524
|
+
// Normalize source path: remove leading/trailing slashes and collapse multiple slashes
|
|
525
|
+
// This ensures consistent path handling across different storage providers
|
|
526
|
+
const normalizedSourcePath = sourcePath.replace(/^\/+|\/+$/g, '').replace(/\/+/g, '/');
|
|
527
|
+
console.log('[copyObjectBetweenProviders] Path normalization:', {
|
|
528
|
+
original: sourcePath,
|
|
529
|
+
normalized: normalizedSourcePath,
|
|
530
|
+
});
|
|
531
|
+
// Get the file from source provider
|
|
532
|
+
const fileData = await sourceDriver.GetObject({ fullPath: normalizedSourcePath });
|
|
533
|
+
if (!fileData || fileData.length === 0) {
|
|
534
|
+
result.message = `Failed to retrieve file from source: ${normalizedSourcePath}`;
|
|
535
|
+
console.error('[copyObjectBetweenProviders]', result.message);
|
|
536
|
+
return result;
|
|
537
|
+
}
|
|
538
|
+
console.log('[copyObjectBetweenProviders] File retrieved, size:', fileData.length, 'bytes');
|
|
539
|
+
// Get metadata for content type
|
|
540
|
+
let contentType = 'application/octet-stream';
|
|
541
|
+
try {
|
|
542
|
+
const metadata = await sourceDriver.GetObjectMetadata({ fullPath: normalizedSourcePath });
|
|
543
|
+
if (metadata.contentType) {
|
|
544
|
+
contentType = metadata.contentType;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
catch {
|
|
548
|
+
// Use mime-types to guess content type from filename
|
|
549
|
+
const mimeType = mime_types_1.default.lookup(sourcePath);
|
|
550
|
+
if (mimeType) {
|
|
551
|
+
contentType = mimeType;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
console.log('[copyObjectBetweenProviders] Uploading to destination with contentType:', contentType);
|
|
555
|
+
// Upload to destination provider
|
|
556
|
+
const uploadSuccess = await destDriver.PutObject(destinationPath, fileData, contentType);
|
|
557
|
+
if (uploadSuccess) {
|
|
558
|
+
result.success = true;
|
|
559
|
+
result.bytesTransferred = fileData.length;
|
|
560
|
+
result.message = `Successfully copied ${fileData.length} bytes from ${sourceProviderEntity.Name} to ${destinationProviderEntity.Name}`;
|
|
561
|
+
console.log('[copyObjectBetweenProviders]', result.message);
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
result.message = `Failed to upload file to destination: ${destinationPath}`;
|
|
565
|
+
console.error('[copyObjectBetweenProviders]', result.message);
|
|
566
|
+
}
|
|
567
|
+
return result;
|
|
568
|
+
}
|
|
569
|
+
catch (error) {
|
|
570
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
571
|
+
result.message = `Transfer failed: ${errorMessage}`;
|
|
572
|
+
console.error('[copyObjectBetweenProviders] Error:', error);
|
|
573
|
+
return result;
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
exports.copyObjectBetweenProviders = copyObjectBetweenProviders;
|
|
577
|
+
/**
|
|
578
|
+
* Searches for files across multiple storage providers in parallel.
|
|
579
|
+
*
|
|
580
|
+
* This utility function enables searching for files across multiple storage providers
|
|
581
|
+
* simultaneously. Each provider is queried in parallel using Promise.allSettled,
|
|
582
|
+
* ensuring that failures in one provider don't affect results from others.
|
|
583
|
+
*
|
|
584
|
+
* Providers that don't support search (SupportsSearch = false) will return a result
|
|
585
|
+
* with success=false and an appropriate error message rather than being silently skipped.
|
|
586
|
+
*
|
|
587
|
+
* @param providerEntities - Array of provider entities to search
|
|
588
|
+
* @param query - Search query string
|
|
589
|
+
* @param options - Optional search configuration including user contexts for OAuth providers
|
|
590
|
+
* @returns A promise that resolves to aggregated results grouped by provider
|
|
591
|
+
*
|
|
592
|
+
* @example
|
|
593
|
+
* ```typescript
|
|
594
|
+
* // Search across Google Drive and Dropbox
|
|
595
|
+
* const providers = [googleDriveProvider, dropboxProvider];
|
|
596
|
+
* const userContexts = new Map([
|
|
597
|
+
* [googleDriveProvider.ID, { userID: currentUser.ID, contextUser }],
|
|
598
|
+
* [dropboxProvider.ID, { userID: currentUser.ID, contextUser }]
|
|
599
|
+
* ]);
|
|
600
|
+
*
|
|
601
|
+
* const result = await searchAcrossProviders(providers, 'quarterly report', {
|
|
602
|
+
* maxResultsPerProvider: 25,
|
|
603
|
+
* fileTypes: ['pdf', 'docx'],
|
|
604
|
+
* providerUserContexts: userContexts
|
|
605
|
+
* });
|
|
606
|
+
*
|
|
607
|
+
* // Process results by provider
|
|
608
|
+
* for (const providerResult of result.providerResults) {
|
|
609
|
+
* if (providerResult.success) {
|
|
610
|
+
* console.log(`${providerResult.providerName}: ${providerResult.results.length} results`);
|
|
611
|
+
* } else {
|
|
612
|
+
* console.log(`${providerResult.providerName}: ${providerResult.errorMessage}`);
|
|
613
|
+
* }
|
|
614
|
+
* }
|
|
615
|
+
* ```
|
|
616
|
+
*/
|
|
617
|
+
const searchAcrossProviders = async (providerEntities, query, options) => {
|
|
618
|
+
console.log('[searchAcrossProviders] Starting search:', {
|
|
619
|
+
providerCount: providerEntities.length,
|
|
620
|
+
providers: providerEntities.map((p) => p.Name),
|
|
621
|
+
query,
|
|
622
|
+
options: { ...options, providerUserContexts: options?.providerUserContexts ? '[Map]' : undefined },
|
|
623
|
+
});
|
|
624
|
+
const maxResults = options?.maxResultsPerProvider ?? 50;
|
|
625
|
+
const searchOptions = {
|
|
626
|
+
maxResults,
|
|
627
|
+
fileTypes: options?.fileTypes,
|
|
628
|
+
searchContent: options?.searchContent ?? false,
|
|
629
|
+
};
|
|
630
|
+
// Create search promises for each provider
|
|
631
|
+
const searchPromises = providerEntities.map(async (providerEntity) => {
|
|
632
|
+
const providerResult = {
|
|
633
|
+
providerID: providerEntity.ID,
|
|
634
|
+
providerName: providerEntity.Name,
|
|
635
|
+
success: false,
|
|
636
|
+
results: [],
|
|
637
|
+
hasMore: false,
|
|
638
|
+
};
|
|
639
|
+
try {
|
|
640
|
+
// Check if provider supports search
|
|
641
|
+
if (!providerEntity.SupportsSearch) {
|
|
642
|
+
providerResult.errorMessage = 'This provider does not support search';
|
|
643
|
+
console.log(`[searchAcrossProviders] ${providerEntity.Name}: Does not support search`);
|
|
644
|
+
return providerResult;
|
|
645
|
+
}
|
|
646
|
+
// Get user context for this provider if available
|
|
647
|
+
const userContext = options?.providerUserContexts?.get(providerEntity.ID);
|
|
648
|
+
// Initialize driver with user credentials if available
|
|
649
|
+
const driver = await initializeDriver(providerEntity, userContext);
|
|
650
|
+
// Execute search
|
|
651
|
+
console.log(`[searchAcrossProviders] ${providerEntity.Name}: Executing search...`);
|
|
652
|
+
const searchResult = await driver.SearchFiles(query, searchOptions);
|
|
653
|
+
providerResult.success = true;
|
|
654
|
+
providerResult.results = searchResult.results;
|
|
655
|
+
providerResult.totalMatches = searchResult.totalMatches;
|
|
656
|
+
providerResult.hasMore = searchResult.hasMore;
|
|
657
|
+
providerResult.nextPageToken = searchResult.nextPageToken;
|
|
658
|
+
console.log(`[searchAcrossProviders] ${providerEntity.Name}: Found ${searchResult.results.length} results`);
|
|
659
|
+
}
|
|
660
|
+
catch (error) {
|
|
661
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
662
|
+
providerResult.errorMessage = errorMessage;
|
|
663
|
+
console.error(`[searchAcrossProviders] ${providerEntity.Name}: Error -`, errorMessage);
|
|
664
|
+
}
|
|
665
|
+
return providerResult;
|
|
666
|
+
});
|
|
667
|
+
// Execute all searches in parallel
|
|
668
|
+
const settledResults = await Promise.allSettled(searchPromises);
|
|
669
|
+
// Aggregate results
|
|
670
|
+
const providerResults = [];
|
|
671
|
+
let totalResultsReturned = 0;
|
|
672
|
+
let successfulProviders = 0;
|
|
673
|
+
let failedProviders = 0;
|
|
674
|
+
for (const settled of settledResults) {
|
|
675
|
+
if (settled.status === 'fulfilled') {
|
|
676
|
+
const result = settled.value;
|
|
677
|
+
providerResults.push(result);
|
|
678
|
+
if (result.success) {
|
|
679
|
+
successfulProviders++;
|
|
680
|
+
totalResultsReturned += result.results.length;
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
failedProviders++;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
// Promise rejected (shouldn't happen with our try/catch, but handle it)
|
|
688
|
+
failedProviders++;
|
|
689
|
+
console.error('[searchAcrossProviders] Promise rejected:', settled.reason);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
const aggregatedResult = {
|
|
693
|
+
providerResults,
|
|
694
|
+
totalResultsReturned,
|
|
695
|
+
successfulProviders,
|
|
696
|
+
failedProviders,
|
|
697
|
+
};
|
|
698
|
+
console.log('[searchAcrossProviders] Search complete:', {
|
|
699
|
+
totalResultsReturned,
|
|
700
|
+
successfulProviders,
|
|
701
|
+
failedProviders,
|
|
702
|
+
});
|
|
703
|
+
return aggregatedResult;
|
|
704
|
+
};
|
|
705
|
+
exports.searchAcrossProviders = searchAcrossProviders;
|
|
706
|
+
/**
|
|
707
|
+
* Searches for files across multiple storage accounts in parallel.
|
|
708
|
+
*
|
|
709
|
+
* This is the enterprise version of search that uses the account-based credential model.
|
|
710
|
+
* Each account is searched independently, allowing multiple accounts from the same
|
|
711
|
+
* provider type to be searched (e.g., two different Dropbox accounts).
|
|
712
|
+
*
|
|
713
|
+
* @param accounts - Array of account/provider pairs to search
|
|
714
|
+
* @param query - Search query string
|
|
715
|
+
* @param options - Search configuration including context user for credentials
|
|
716
|
+
* @returns A promise that resolves to aggregated results grouped by account
|
|
717
|
+
*
|
|
718
|
+
* @example
|
|
719
|
+
* ```typescript
|
|
720
|
+
* const accounts = [
|
|
721
|
+
* { accountEntity: researchDropbox, providerEntity: dropboxProvider },
|
|
722
|
+
* { accountEntity: marketingDropbox, providerEntity: dropboxProvider },
|
|
723
|
+
* { accountEntity: engineeringGDrive, providerEntity: gdriveProvider }
|
|
724
|
+
* ];
|
|
725
|
+
*
|
|
726
|
+
* const result = await searchAcrossAccounts(accounts, 'quarterly report', {
|
|
727
|
+
* maxResultsPerAccount: 25,
|
|
728
|
+
* fileTypes: ['pdf', 'docx'],
|
|
729
|
+
* contextUser: currentUser
|
|
730
|
+
* });
|
|
731
|
+
*
|
|
732
|
+
* for (const accountResult of result.accountResults) {
|
|
733
|
+
* if (accountResult.success) {
|
|
734
|
+
* console.log(`${accountResult.accountName}: ${accountResult.results.length} results`);
|
|
735
|
+
* }
|
|
736
|
+
* }
|
|
737
|
+
* ```
|
|
738
|
+
*/
|
|
739
|
+
const searchAcrossAccounts = async (accounts, query, options) => {
|
|
740
|
+
console.log('[searchAcrossAccounts] Starting search:', {
|
|
741
|
+
accountCount: accounts.length,
|
|
742
|
+
accounts: accounts.map((a) => ({ account: a.accountEntity.Name, provider: a.providerEntity.Name })),
|
|
743
|
+
query,
|
|
744
|
+
maxResultsPerAccount: options.maxResultsPerAccount,
|
|
745
|
+
fileTypes: options.fileTypes,
|
|
746
|
+
searchContent: options.searchContent,
|
|
747
|
+
});
|
|
748
|
+
const maxResults = options.maxResultsPerAccount ?? 50;
|
|
749
|
+
const searchOptions = {
|
|
750
|
+
maxResults,
|
|
751
|
+
fileTypes: options.fileTypes,
|
|
752
|
+
searchContent: options.searchContent ?? false,
|
|
753
|
+
};
|
|
754
|
+
// Create search promises for each account
|
|
755
|
+
const searchPromises = accounts.map(async ({ accountEntity, providerEntity }) => {
|
|
756
|
+
const accountResult = {
|
|
757
|
+
accountID: accountEntity.ID,
|
|
758
|
+
accountName: accountEntity.Name,
|
|
759
|
+
providerID: providerEntity.ID,
|
|
760
|
+
providerName: providerEntity.Name,
|
|
761
|
+
success: false,
|
|
762
|
+
results: [],
|
|
763
|
+
hasMore: false,
|
|
764
|
+
};
|
|
765
|
+
try {
|
|
766
|
+
// Check if provider supports search
|
|
767
|
+
if (!providerEntity.SupportsSearch) {
|
|
768
|
+
accountResult.errorMessage = 'This provider does not support search';
|
|
769
|
+
console.log(`[searchAcrossAccounts] ${accountEntity.Name}: Provider does not support search`);
|
|
770
|
+
return accountResult;
|
|
771
|
+
}
|
|
772
|
+
// Initialize driver with account-based credentials
|
|
773
|
+
const driver = await initializeDriverWithAccountCredentials({
|
|
774
|
+
accountEntity,
|
|
775
|
+
providerEntity,
|
|
776
|
+
contextUser: options.contextUser,
|
|
777
|
+
});
|
|
778
|
+
// Execute search
|
|
779
|
+
console.log(`[searchAcrossAccounts] ${accountEntity.Name}: Executing search...`);
|
|
780
|
+
const searchResult = await driver.SearchFiles(query, searchOptions);
|
|
781
|
+
accountResult.success = true;
|
|
782
|
+
accountResult.results = searchResult.results;
|
|
783
|
+
accountResult.totalMatches = searchResult.totalMatches;
|
|
784
|
+
accountResult.hasMore = searchResult.hasMore;
|
|
785
|
+
accountResult.nextPageToken = searchResult.nextPageToken;
|
|
786
|
+
console.log(`[searchAcrossAccounts] ${accountEntity.Name}: Found ${searchResult.results.length} results`);
|
|
787
|
+
}
|
|
788
|
+
catch (error) {
|
|
789
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
790
|
+
accountResult.errorMessage = errorMessage;
|
|
791
|
+
console.error(`[searchAcrossAccounts] ${accountEntity.Name}: Error -`, errorMessage);
|
|
792
|
+
}
|
|
793
|
+
return accountResult;
|
|
794
|
+
});
|
|
795
|
+
// Execute all searches in parallel
|
|
796
|
+
const settledResults = await Promise.allSettled(searchPromises);
|
|
797
|
+
// Aggregate results
|
|
798
|
+
const accountResults = [];
|
|
799
|
+
let totalResultsReturned = 0;
|
|
800
|
+
let successfulAccounts = 0;
|
|
801
|
+
let failedAccounts = 0;
|
|
802
|
+
for (const settled of settledResults) {
|
|
803
|
+
if (settled.status === 'fulfilled') {
|
|
804
|
+
const result = settled.value;
|
|
805
|
+
accountResults.push(result);
|
|
806
|
+
if (result.success) {
|
|
807
|
+
successfulAccounts++;
|
|
808
|
+
totalResultsReturned += result.results.length;
|
|
809
|
+
}
|
|
810
|
+
else {
|
|
811
|
+
failedAccounts++;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
// Promise rejected (shouldn't happen with our try/catch, but handle it)
|
|
816
|
+
failedAccounts++;
|
|
817
|
+
console.error('[searchAcrossAccounts] Promise rejected:', settled.reason);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
const aggregatedResult = {
|
|
821
|
+
accountResults,
|
|
822
|
+
totalResultsReturned,
|
|
823
|
+
successfulAccounts,
|
|
824
|
+
failedAccounts,
|
|
825
|
+
};
|
|
826
|
+
console.log('[searchAcrossAccounts] Search complete:', {
|
|
827
|
+
totalResultsReturned,
|
|
828
|
+
successfulAccounts,
|
|
829
|
+
failedAccounts,
|
|
830
|
+
});
|
|
831
|
+
return aggregatedResult;
|
|
832
|
+
};
|
|
833
|
+
exports.searchAcrossAccounts = searchAcrossAccounts;
|
|
173
834
|
//# sourceMappingURL=util.js.map
|