@sinoia/hubdoc-tools 1.3.2 → 1.3.4
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/index.d.ts +1 -1
- package/dist/plugins/alfresco/plugin.json +12 -0
- package/dist/plugins/aws-s3/plugin.json +12 -0
- package/dist/plugins/azure-blob/plugin.json +12 -0
- package/dist/plugins/box/plugin.json +12 -0
- package/dist/plugins/core/index.d.ts +25 -0
- package/dist/plugins/core/index.d.ts.map +1 -0
- package/dist/plugins/core/index.js +400 -0
- package/dist/plugins/core/index.js.map +1 -0
- package/dist/plugins/core/plugin.json +26 -0
- package/dist/plugins/dropbox/plugin.json +12 -0
- package/dist/plugins/filesystem/index.d.ts +22 -0
- package/dist/plugins/filesystem/index.d.ts.map +1 -0
- package/dist/plugins/filesystem/index.js +306 -0
- package/dist/plugins/filesystem/index.js.map +1 -0
- package/dist/plugins/filesystem/plugin.json +12 -0
- package/dist/plugins/googledrive/plugin.json +12 -0
- package/dist/plugins/nuxeo/plugin.json +12 -0
- package/dist/plugins/onedrive/plugin.json +12 -0
- package/dist/plugins/opentext/plugin.json +12 -0
- package/dist/plugins/sharepoint/plugin.json +12 -0
- package/dist/services/hubdoc-api.d.ts +1 -1
- package/dist/services/hubdoc-api.js +1 -1
- package/dist/services/oauth-token-service.d.ts +1 -1
- package/dist/services/oauth-token-service.js +2 -2
- package/dist/services/permission-manager.d.ts +1 -1
- package/dist/services/permission-manager.js +1 -1
- package/dist/src/types/plugins.d.ts +111 -0
- package/dist/src/types/plugins.d.ts.map +1 -0
- package/dist/src/types/plugins.js +3 -0
- package/dist/src/types/plugins.js.map +1 -0
- package/dist/src/utils/concurrent-processor.d.ts +63 -0
- package/dist/src/utils/concurrent-processor.d.ts.map +1 -0
- package/dist/src/utils/concurrent-processor.js +240 -0
- package/dist/src/utils/concurrent-processor.js.map +1 -0
- package/dist/src/utils/xml-metadata.d.ts +47 -0
- package/dist/src/utils/xml-metadata.d.ts.map +1 -0
- package/dist/src/utils/xml-metadata.js +200 -0
- package/dist/src/utils/xml-metadata.js.map +1 -0
- package/dist/types/index.d.ts +1 -1
- package/package.json +6 -2
- package/plugins/alfresco/index.ts +518 -0
- package/plugins/alfresco/plugin.json +12 -0
- package/plugins/aws-s3/index.ts +471 -0
- package/plugins/aws-s3/plugin.json +12 -0
- package/plugins/azure-blob/index.ts +420 -0
- package/plugins/azure-blob/plugin.json +12 -0
- package/plugins/box/index.ts +495 -0
- package/plugins/box/plugin.json +12 -0
- package/plugins/core/README.md +122 -0
- package/plugins/core/TESTING.md +155 -0
- package/plugins/core/index.ts +510 -0
- package/plugins/core/plugin.json +26 -0
- package/plugins/dropbox/index.ts +451 -0
- package/plugins/dropbox/plugin.json +12 -0
- package/plugins/filesystem/index.ts +360 -0
- package/plugins/filesystem/plugin.json +12 -0
- package/plugins/googledrive/index.ts +463 -0
- package/plugins/googledrive/plugin.json +12 -0
- package/plugins/nuxeo/index.ts +512 -0
- package/plugins/nuxeo/plugin.json +12 -0
- package/plugins/onedrive/TESTING.md +197 -0
- package/plugins/onedrive/index.ts +447 -0
- package/plugins/onedrive/plugin.json +12 -0
- package/plugins/opentext/index.ts +542 -0
- package/plugins/opentext/plugin.json +12 -0
- package/plugins/sharepoint/index.ts +509 -0
- package/plugins/sharepoint/plugin.json +12 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import axios, { AxiosInstance } from 'axios';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import {
|
|
5
|
+
DocumentSourcePlugin,
|
|
6
|
+
DocumentSource,
|
|
7
|
+
PluginConfig,
|
|
8
|
+
ScanResult,
|
|
9
|
+
PluginImportOptions,
|
|
10
|
+
PluginExportOptions,
|
|
11
|
+
ImportResult,
|
|
12
|
+
ExportResult
|
|
13
|
+
} from '../../src/types/plugins';
|
|
14
|
+
|
|
15
|
+
interface OpenTextConfig extends PluginConfig {
|
|
16
|
+
baseUrl: string;
|
|
17
|
+
username: string;
|
|
18
|
+
password: string;
|
|
19
|
+
ticket?: string; // Alternative authentication using OTDS ticket
|
|
20
|
+
startingFolderId?: number; // Starting folder ID, defaults to Enterprise workspace (2000)
|
|
21
|
+
limit?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface OpenTextNode {
|
|
25
|
+
id: number;
|
|
26
|
+
name: string;
|
|
27
|
+
type: number;
|
|
28
|
+
type_name: string;
|
|
29
|
+
container: boolean;
|
|
30
|
+
size?: number;
|
|
31
|
+
create_date: string;
|
|
32
|
+
modify_date: string;
|
|
33
|
+
created_by: number;
|
|
34
|
+
created_by_name: string;
|
|
35
|
+
modified_by: number;
|
|
36
|
+
modified_by_name: string;
|
|
37
|
+
parent_id: number;
|
|
38
|
+
mime_type?: string;
|
|
39
|
+
version_number?: number;
|
|
40
|
+
description?: string;
|
|
41
|
+
reserved?: boolean;
|
|
42
|
+
reserved_by?: number;
|
|
43
|
+
reserved_date?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default class OpenTextPlugin implements DocumentSourcePlugin {
|
|
47
|
+
readonly name = 'opentext';
|
|
48
|
+
readonly version = '1.0.0';
|
|
49
|
+
readonly description = 'OpenText Content Server document source';
|
|
50
|
+
readonly supportedOperations = ['import', 'export', 'both'] as const;
|
|
51
|
+
|
|
52
|
+
private config?: OpenTextConfig;
|
|
53
|
+
private apiClient?: AxiosInstance;
|
|
54
|
+
private authToken?: string;
|
|
55
|
+
|
|
56
|
+
async testConnection(config: PluginConfig): Promise<boolean> {
|
|
57
|
+
try {
|
|
58
|
+
const client = await this.createApiClient(config as OpenTextConfig);
|
|
59
|
+
const response = await client.get('/nodes/2000'); // Test access to Enterprise workspace
|
|
60
|
+
return response.status === 200;
|
|
61
|
+
} catch (error: any) {
|
|
62
|
+
console.error(`OpenText connection test failed: ${error.message}`);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async scan(config: PluginConfig, options?: PluginImportOptions): Promise<ScanResult> {
|
|
68
|
+
this.config = config as OpenTextConfig;
|
|
69
|
+
this.apiClient = await this.createApiClient(this.config);
|
|
70
|
+
|
|
71
|
+
const sources: DocumentSource[] = [];
|
|
72
|
+
const errors: string[] = [];
|
|
73
|
+
let totalSize = 0;
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const limit = (this.config as any).limit || (options as any)?.limit;
|
|
77
|
+
console.log(`🔍 Scanning OpenText Content Server${limit ? ` (limit: ${limit})` : ''}...`);
|
|
78
|
+
|
|
79
|
+
const startingFolderId = this.config.startingFolderId || 2000; // Enterprise workspace
|
|
80
|
+
const nodes = await this.scanNode(startingFolderId);
|
|
81
|
+
|
|
82
|
+
let processedCount = 0;
|
|
83
|
+
for (const node of nodes) {
|
|
84
|
+
if (!node.container && node.type === 144) { // Type 144 is Document
|
|
85
|
+
const source: DocumentSource = {
|
|
86
|
+
id: node.id.toString(),
|
|
87
|
+
name: node.name,
|
|
88
|
+
path: await this.getNodePath(node.id),
|
|
89
|
+
size: node.size || 0,
|
|
90
|
+
mimeType: node.mime_type || this.getMimeType(node.name),
|
|
91
|
+
lastModified: new Date(node.modify_date),
|
|
92
|
+
metadata: {
|
|
93
|
+
opentextId: node.id,
|
|
94
|
+
nodeType: node.type,
|
|
95
|
+
typeName: node.type_name,
|
|
96
|
+
createdAt: node.create_date,
|
|
97
|
+
createdBy: node.created_by_name,
|
|
98
|
+
modifiedBy: node.modified_by_name,
|
|
99
|
+
parentId: node.parent_id,
|
|
100
|
+
versionNumber: node.version_number,
|
|
101
|
+
description: node.description,
|
|
102
|
+
reserved: node.reserved,
|
|
103
|
+
reservedBy: node.reserved_by,
|
|
104
|
+
reservedDate: node.reserved_date
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Apply filters
|
|
109
|
+
if (this.shouldIncludeSource(source, options)) {
|
|
110
|
+
sources.push(source);
|
|
111
|
+
totalSize += source.size;
|
|
112
|
+
processedCount++;
|
|
113
|
+
|
|
114
|
+
// Check limit
|
|
115
|
+
if (limit && processedCount >= limit) {
|
|
116
|
+
console.log(`📏 Reached limit of ${limit} files`);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
sources,
|
|
125
|
+
totalCount: sources.length,
|
|
126
|
+
totalSize,
|
|
127
|
+
errors
|
|
128
|
+
};
|
|
129
|
+
} catch (error: any) {
|
|
130
|
+
return {
|
|
131
|
+
sources: [],
|
|
132
|
+
totalCount: 0,
|
|
133
|
+
totalSize: 0,
|
|
134
|
+
errors: [`OpenText scan failed: ${error.message}`]
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private async scanNode(nodeId: number, parentPath: string = ''): Promise<OpenTextNode[]> {
|
|
140
|
+
if (!this.apiClient) throw new Error('API client not initialized');
|
|
141
|
+
|
|
142
|
+
const allNodes: OpenTextNode[] = [];
|
|
143
|
+
let page = 1;
|
|
144
|
+
const limit = 100;
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
while (true) {
|
|
148
|
+
const response = await this.apiClient.get(`/nodes/${nodeId}/nodes`, {
|
|
149
|
+
params: {
|
|
150
|
+
limit: limit,
|
|
151
|
+
page: page,
|
|
152
|
+
expand: 'properties{original_id,parent_id,name,type,type_name,container,size,create_date,modify_date,created_by,created_by_name,modified_by,modified_by_name,mime_type,version_number,description,reserved,reserved_by,reserved_date}'
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const nodes: OpenTextNode[] = response.data.results || [];
|
|
157
|
+
|
|
158
|
+
if (nodes.length === 0) {
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
for (const node of nodes) {
|
|
163
|
+
const nodePath = parentPath ? `${parentPath}/${node.name}` : node.name;
|
|
164
|
+
(node as any).fullPath = nodePath;
|
|
165
|
+
|
|
166
|
+
if (!node.container) {
|
|
167
|
+
// It's a document
|
|
168
|
+
allNodes.push(node);
|
|
169
|
+
} else {
|
|
170
|
+
// It's a folder, recursively scan it
|
|
171
|
+
const subNodes = await this.scanNode(node.id, nodePath);
|
|
172
|
+
allNodes.push(...subNodes);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Check if there are more pages
|
|
177
|
+
if (nodes.length < limit) {
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
page++;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return allNodes;
|
|
184
|
+
} catch (error: any) {
|
|
185
|
+
if (error.response?.status === 404) {
|
|
186
|
+
console.warn(`Warning: Node not found: ${nodeId}`);
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async import(
|
|
194
|
+
config: PluginConfig,
|
|
195
|
+
sources: DocumentSource[],
|
|
196
|
+
targetDir: string,
|
|
197
|
+
options?: PluginImportOptions
|
|
198
|
+
): Promise<ImportResult[]> {
|
|
199
|
+
this.config = config as OpenTextConfig;
|
|
200
|
+
this.apiClient = await this.createApiClient(this.config);
|
|
201
|
+
|
|
202
|
+
const results: ImportResult[] = [];
|
|
203
|
+
const batchSize = options?.batchSize || 5;
|
|
204
|
+
|
|
205
|
+
// Process in batches to respect API limits
|
|
206
|
+
for (let i = 0; i < sources.length; i += batchSize) {
|
|
207
|
+
const batch = sources.slice(i, i + batchSize);
|
|
208
|
+
|
|
209
|
+
for (const source of batch) {
|
|
210
|
+
const result = await this.importSingle(source, targetDir);
|
|
211
|
+
results.push(result);
|
|
212
|
+
|
|
213
|
+
// Small delay to respect rate limits
|
|
214
|
+
await this.sleep(300);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return results;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private async importSingle(source: DocumentSource, targetDir: string): Promise<ImportResult> {
|
|
222
|
+
try {
|
|
223
|
+
if (!this.apiClient) throw new Error('API client not initialized');
|
|
224
|
+
|
|
225
|
+
const targetPath = path.join(targetDir, source.path);
|
|
226
|
+
const targetDirectory = path.dirname(targetPath);
|
|
227
|
+
|
|
228
|
+
await fs.ensureDir(targetDirectory);
|
|
229
|
+
|
|
230
|
+
// Download file content from OpenText
|
|
231
|
+
const response = await this.apiClient.get(`/nodes/${source.id}/content`, {
|
|
232
|
+
responseType: 'stream'
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const writer = fs.createWriteStream(targetPath);
|
|
236
|
+
response.data.pipe(writer);
|
|
237
|
+
|
|
238
|
+
return new Promise((resolve) => {
|
|
239
|
+
writer.on('finish', () => {
|
|
240
|
+
resolve({
|
|
241
|
+
success: true,
|
|
242
|
+
source,
|
|
243
|
+
localPath: targetPath,
|
|
244
|
+
bytesTransferred: source.size
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
writer.on('error', (error) => {
|
|
249
|
+
resolve({
|
|
250
|
+
success: false,
|
|
251
|
+
source,
|
|
252
|
+
error: error.message
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
} catch (error: any) {
|
|
257
|
+
return {
|
|
258
|
+
success: false,
|
|
259
|
+
source,
|
|
260
|
+
error: error.message
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async export?(
|
|
266
|
+
config: PluginConfig,
|
|
267
|
+
localSources: DocumentSource[],
|
|
268
|
+
options?: PluginExportOptions
|
|
269
|
+
): Promise<ExportResult[]> {
|
|
270
|
+
this.config = config as OpenTextConfig;
|
|
271
|
+
this.apiClient = await this.createApiClient(this.config);
|
|
272
|
+
|
|
273
|
+
const results: ExportResult[] = [];
|
|
274
|
+
const rootFolderId = this.config.startingFolderId || 2000;
|
|
275
|
+
|
|
276
|
+
for (const source of localSources) {
|
|
277
|
+
try {
|
|
278
|
+
// Determine target folder
|
|
279
|
+
let targetFolderId = rootFolderId;
|
|
280
|
+
|
|
281
|
+
if (options?.preserveStructure && source.path.includes('/')) {
|
|
282
|
+
const folderPath = path.dirname(source.path);
|
|
283
|
+
targetFolderId = await this.createFolderStructure(folderPath, rootFolderId);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Read local file
|
|
287
|
+
const fileContent = await fs.readFile(source.id);
|
|
288
|
+
// Normalize filename to NFC to handle accented characters consistently across platforms
|
|
289
|
+
const fileName = (options?.preserveStructure ? path.basename(source.path) : source.name).normalize('NFC');
|
|
290
|
+
|
|
291
|
+
// Upload file to OpenText using multipart form data
|
|
292
|
+
const FormData = require('form-data');
|
|
293
|
+
const form = new FormData();
|
|
294
|
+
|
|
295
|
+
form.append('type', '144'); // Document type
|
|
296
|
+
form.append('name', fileName);
|
|
297
|
+
form.append('description', `Uploaded via hubdoc-tools`);
|
|
298
|
+
form.append('file', fileContent, fileName);
|
|
299
|
+
|
|
300
|
+
const response = await this.apiClient!.post(`/nodes/${targetFolderId}/nodes`, form, {
|
|
301
|
+
headers: {
|
|
302
|
+
...form.getHeaders()
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const targetPath = options?.preserveStructure ? source.path : source.name;
|
|
307
|
+
|
|
308
|
+
results.push({
|
|
309
|
+
success: true,
|
|
310
|
+
targetPath,
|
|
311
|
+
source,
|
|
312
|
+
bytesTransferred: source.size
|
|
313
|
+
});
|
|
314
|
+
} catch (error: any) {
|
|
315
|
+
results.push({
|
|
316
|
+
success: false,
|
|
317
|
+
targetPath: options?.targetPath || '',
|
|
318
|
+
source,
|
|
319
|
+
error: error.message
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return results;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private async createFolderStructure(folderPath: string, parentId: number): Promise<number> {
|
|
328
|
+
if (!this.apiClient) throw new Error('API client not initialized');
|
|
329
|
+
|
|
330
|
+
const parts = folderPath.split('/').filter(part => part.length > 0);
|
|
331
|
+
let currentParentId = parentId;
|
|
332
|
+
|
|
333
|
+
for (const folderName of parts) {
|
|
334
|
+
try {
|
|
335
|
+
// Check if folder already exists
|
|
336
|
+
const response = await this.apiClient.get(`/nodes/${currentParentId}/nodes`, {
|
|
337
|
+
params: {
|
|
338
|
+
where_name: folderName,
|
|
339
|
+
where_type: 0, // Folder type
|
|
340
|
+
limit: 1
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const existingFolder = response.data.results?.[0];
|
|
345
|
+
|
|
346
|
+
if (existingFolder) {
|
|
347
|
+
currentParentId = existingFolder.id;
|
|
348
|
+
} else {
|
|
349
|
+
// Create new folder
|
|
350
|
+
const createResponse = await this.apiClient.post(`/nodes/${currentParentId}/nodes`, {
|
|
351
|
+
type: 0, // Folder type
|
|
352
|
+
name: folderName,
|
|
353
|
+
description: `Created by hubdoc-tools`
|
|
354
|
+
});
|
|
355
|
+
currentParentId = createResponse.data.id;
|
|
356
|
+
}
|
|
357
|
+
} catch (error: any) {
|
|
358
|
+
throw new Error(`Failed to create folder structure: ${error.message}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return currentParentId;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private async getNodePath(nodeId: number): Promise<string> {
|
|
366
|
+
if (!this.apiClient) throw new Error('API client not initialized');
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
// Get the node's ancestors to build the full path
|
|
370
|
+
const response = await this.apiClient.get(`/nodes/${nodeId}/ancestors`);
|
|
371
|
+
const ancestors = response.data.ancestors || [];
|
|
372
|
+
|
|
373
|
+
// Filter out the Enterprise workspace and build path
|
|
374
|
+
const pathParts = ancestors
|
|
375
|
+
.filter((ancestor: any) => ancestor.id !== 2000) // Skip Enterprise workspace
|
|
376
|
+
.map((ancestor: any) => ancestor.name);
|
|
377
|
+
|
|
378
|
+
// Get the node itself to add its name
|
|
379
|
+
const nodeResponse = await this.apiClient.get(`/nodes/${nodeId}`);
|
|
380
|
+
const nodeName = nodeResponse.data.results.name;
|
|
381
|
+
|
|
382
|
+
pathParts.push(nodeName);
|
|
383
|
+
|
|
384
|
+
return pathParts.join('/');
|
|
385
|
+
} catch (error: any) {
|
|
386
|
+
// Fallback to just the node ID if path resolution fails
|
|
387
|
+
return `node_${nodeId}`;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
getConfigSchema(): Record<string, any> {
|
|
392
|
+
return {
|
|
393
|
+
type: 'object',
|
|
394
|
+
properties: {
|
|
395
|
+
baseUrl: {
|
|
396
|
+
type: 'string',
|
|
397
|
+
description: 'OpenText Content Server base URL (e.g., http://localhost/otcs/cs.exe/api/v1)',
|
|
398
|
+
required: true
|
|
399
|
+
},
|
|
400
|
+
username: {
|
|
401
|
+
type: 'string',
|
|
402
|
+
description: 'OpenText username',
|
|
403
|
+
required: false
|
|
404
|
+
},
|
|
405
|
+
password: {
|
|
406
|
+
type: 'string',
|
|
407
|
+
description: 'OpenText password',
|
|
408
|
+
required: false
|
|
409
|
+
},
|
|
410
|
+
ticket: {
|
|
411
|
+
type: 'string',
|
|
412
|
+
description: 'OTDS authentication ticket (alternative to username/password)',
|
|
413
|
+
required: false
|
|
414
|
+
},
|
|
415
|
+
startingFolderId: {
|
|
416
|
+
type: 'number',
|
|
417
|
+
description: 'Starting folder ID (default: 2000 for Enterprise workspace)',
|
|
418
|
+
default: 2000
|
|
419
|
+
},
|
|
420
|
+
limit: {
|
|
421
|
+
type: 'number',
|
|
422
|
+
description: 'Maximum number of documents to scan (useful for testing)',
|
|
423
|
+
required: false
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
required: ['baseUrl'],
|
|
427
|
+
oneOf: [
|
|
428
|
+
{ required: ['baseUrl', 'username', 'password'] },
|
|
429
|
+
{ required: ['baseUrl', 'ticket'] }
|
|
430
|
+
]
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async initialize(config: PluginConfig): Promise<void> {
|
|
435
|
+
this.config = config as OpenTextConfig;
|
|
436
|
+
|
|
437
|
+
if (!this.config.baseUrl) {
|
|
438
|
+
throw new Error('OpenText base URL is required');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (!this.config.username && !this.config.ticket) {
|
|
442
|
+
throw new Error('Either username/password or OTDS ticket is required');
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (this.config.username && !this.config.password) {
|
|
446
|
+
throw new Error('Password is required when using username authentication');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
this.apiClient = await this.createApiClient(this.config);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async destroy(): Promise<void> {
|
|
453
|
+
// Log out if we have an auth token
|
|
454
|
+
if (this.authToken && this.apiClient) {
|
|
455
|
+
try {
|
|
456
|
+
await this.apiClient.post('/auth');
|
|
457
|
+
} catch (error) {
|
|
458
|
+
// Ignore logout errors
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
this.config = undefined;
|
|
463
|
+
this.apiClient = undefined;
|
|
464
|
+
this.authToken = undefined;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
private async createApiClient(config: OpenTextConfig): Promise<AxiosInstance> {
|
|
468
|
+
const client = axios.create({
|
|
469
|
+
baseURL: config.baseUrl,
|
|
470
|
+
headers: {
|
|
471
|
+
'Content-Type': 'application/json'
|
|
472
|
+
},
|
|
473
|
+
timeout: 30000
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// Authenticate and get token
|
|
477
|
+
if (config.ticket) {
|
|
478
|
+
// Use OTDS ticket
|
|
479
|
+
client.defaults.headers.common['OTDSTicket'] = config.ticket;
|
|
480
|
+
} else if (config.username && config.password) {
|
|
481
|
+
// Login with username/password
|
|
482
|
+
const authResponse = await client.post('/auth', {
|
|
483
|
+
username: config.username,
|
|
484
|
+
password: config.password
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
this.authToken = authResponse.data.ticket;
|
|
488
|
+
client.defaults.headers.common['OTCSTicket'] = this.authToken;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return client;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
private getMimeType(fileName: string): string {
|
|
495
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
496
|
+
const mimeTypes: Record<string, string> = {
|
|
497
|
+
'.pdf': 'application/pdf',
|
|
498
|
+
'.doc': 'application/msword',
|
|
499
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
500
|
+
'.xls': 'application/vnd.ms-excel',
|
|
501
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
502
|
+
'.ppt': 'application/vnd.ms-powerpoint',
|
|
503
|
+
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
504
|
+
'.txt': 'text/plain',
|
|
505
|
+
'.csv': 'text/csv',
|
|
506
|
+
'.json': 'application/json',
|
|
507
|
+
'.xml': 'application/xml',
|
|
508
|
+
'.jpg': 'image/jpeg',
|
|
509
|
+
'.jpeg': 'image/jpeg',
|
|
510
|
+
'.png': 'image/png',
|
|
511
|
+
'.gif': 'image/gif',
|
|
512
|
+
'.zip': 'application/zip'
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
return mimeTypes[ext] || 'application/octet-stream';
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
private shouldIncludeSource(source: DocumentSource, options?: PluginImportOptions): boolean {
|
|
519
|
+
// Apply size filter
|
|
520
|
+
if (options?.filters?.maxSize && source.size > options.filters.maxSize) {
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Apply date range filter
|
|
525
|
+
if (options?.filters?.dateRange) {
|
|
526
|
+
const { from, to } = options.filters.dateRange;
|
|
527
|
+
if (from && source.lastModified < from) return false;
|
|
528
|
+
if (to && source.lastModified > to) return false;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Apply MIME type filter
|
|
532
|
+
if (options?.filters?.mimeTypes && !options.filters.mimeTypes.includes(source.mimeType)) {
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
private sleep(ms: number): Promise<void> {
|
|
540
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
541
|
+
}
|
|
542
|
+
}
|