@sinoia/hubdoc-tools 1.3.6 → 1.3.7
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/package.json +3 -4
- package/plugins/alfresco/index.ts +0 -518
- package/plugins/alfresco/plugin.json +0 -12
- package/plugins/aws-s3/index.ts +0 -471
- package/plugins/aws-s3/plugin.json +0 -12
- package/plugins/azure-blob/index.ts +0 -420
- package/plugins/azure-blob/plugin.json +0 -12
- package/plugins/box/index.ts +0 -495
- package/plugins/box/plugin.json +0 -12
- package/plugins/core/README.md +0 -122
- package/plugins/core/TESTING.md +0 -155
- package/plugins/core/index.ts +0 -510
- package/plugins/core/plugin.json +0 -26
- package/plugins/dropbox/index.ts +0 -451
- package/plugins/dropbox/plugin.json +0 -12
- package/plugins/filesystem/index.ts +0 -360
- package/plugins/filesystem/plugin.json +0 -12
- package/plugins/googledrive/index.ts +0 -463
- package/plugins/googledrive/plugin.json +0 -12
- package/plugins/nuxeo/index.ts +0 -512
- package/plugins/nuxeo/plugin.json +0 -12
- package/plugins/onedrive/TESTING.md +0 -197
- package/plugins/onedrive/index.ts +0 -447
- package/plugins/onedrive/plugin.json +0 -12
- package/plugins/opentext/index.ts +0 -542
- package/plugins/opentext/plugin.json +0 -12
- package/plugins/sharepoint/index.ts +0 -509
- package/plugins/sharepoint/plugin.json +0 -12
package/plugins/core/TESTING.md
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
# Guide de test du plugin Core
|
|
2
|
-
|
|
3
|
-
Ce guide vous permet de tester le plugin Core avec l'instance https://rec.plugandwork.fr.
|
|
4
|
-
|
|
5
|
-
## Prérequis
|
|
6
|
-
|
|
7
|
-
1. Token d'authentification Core valide
|
|
8
|
-
2. HubDoc Tools configuré
|
|
9
|
-
|
|
10
|
-
## Configuration de test
|
|
11
|
-
|
|
12
|
-
### 1. Créer un fichier de configuration
|
|
13
|
-
|
|
14
|
-
```json
|
|
15
|
-
{
|
|
16
|
-
"baseUrl": "https://rec.plugandwork.fr",
|
|
17
|
-
"bearerToken": "YOUR_BEARER_TOKEN_HERE",
|
|
18
|
-
"limit": 10
|
|
19
|
-
}
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
### 2. Tester la connexion
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
# Lister les plugins disponibles
|
|
26
|
-
npm run dev -- plugins
|
|
27
|
-
|
|
28
|
-
# Vérifier que Core est bien listé
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### 3. Scanner les documents (test rapide)
|
|
32
|
-
|
|
33
|
-
Créer un script de test :
|
|
34
|
-
|
|
35
|
-
```javascript
|
|
36
|
-
// test-core.js
|
|
37
|
-
const fs = require('fs-extra');
|
|
38
|
-
const { register } = require('ts-node');
|
|
39
|
-
register({ transpileOnly: true, compilerOptions: { esModuleInterop: true } });
|
|
40
|
-
|
|
41
|
-
const CorePlugin = require('./plugins/core/index.ts').default;
|
|
42
|
-
|
|
43
|
-
async function test() {
|
|
44
|
-
const config = {
|
|
45
|
-
baseUrl: "https://rec.plugandwork.fr",
|
|
46
|
-
bearerToken: "YOUR_TOKEN",
|
|
47
|
-
limit: 5
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const plugin = new CorePlugin();
|
|
51
|
-
|
|
52
|
-
console.log('Testing connection...');
|
|
53
|
-
const connected = await plugin.testConnection(config);
|
|
54
|
-
console.log('Connected:', connected);
|
|
55
|
-
|
|
56
|
-
if (connected) {
|
|
57
|
-
console.log('Scanning documents...');
|
|
58
|
-
const result = await plugin.scan(config);
|
|
59
|
-
console.log(`Found ${result.totalCount} documents`);
|
|
60
|
-
result.sources.slice(0, 3).forEach(doc => {
|
|
61
|
-
console.log(`- ${doc.name} (${doc.path})`);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
test().catch(console.error);
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
Puis exécuter :
|
|
70
|
-
```bash
|
|
71
|
-
node test-core.js
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## Résultats attendus
|
|
75
|
-
|
|
76
|
-
### Scan réussi
|
|
77
|
-
- ✅ Connection: SUCCESS
|
|
78
|
-
- ✅ Documents trouvés avec métadonnées Core
|
|
79
|
-
- ✅ Limitation respectée (paramètre `limit`)
|
|
80
|
-
- ✅ Types de documents variés (file, contact, note, email, etc.)
|
|
81
|
-
|
|
82
|
-
### Structure des documents
|
|
83
|
-
- **Chemin** : `{type}/{subtype}/{title}.txt`
|
|
84
|
-
- **Métadonnées** : Core ID, type, tags, etc.
|
|
85
|
-
- **Types MIME** : text/plain par défaut
|
|
86
|
-
|
|
87
|
-
## Test d'intégration complète
|
|
88
|
-
|
|
89
|
-
### 1. Configuration d'une connexion
|
|
90
|
-
```bash
|
|
91
|
-
echo '{"baseUrl":"https://rec.plugandwork.fr","bearerToken":"YOUR_TOKEN","limit":10}' > core-config.json
|
|
92
|
-
npm run dev -- connect core --config core-config.json --name test-core
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### 2. Scan des documents
|
|
96
|
-
```bash
|
|
97
|
-
npm run dev -- plugin-scan test-core --output core-mapping.csv
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### 3. Vérification du mapping
|
|
101
|
-
```bash
|
|
102
|
-
head -10 core-mapping.csv
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
Le fichier doit contenir :
|
|
106
|
-
- Colonnes : File Path, Target Folder, Workspace, Metadata (JSON), Permissions
|
|
107
|
-
- Métadonnées JSON avec Core ID, type, tags
|
|
108
|
-
- Chemins organisés par type Core
|
|
109
|
-
|
|
110
|
-
## Dépannage
|
|
111
|
-
|
|
112
|
-
### Token expiré
|
|
113
|
-
```
|
|
114
|
-
Error: Request failed with status code 401
|
|
115
|
-
```
|
|
116
|
-
→ Vérifier/renouveler le token Bearer
|
|
117
|
-
|
|
118
|
-
### URL incorrecte
|
|
119
|
-
```
|
|
120
|
-
Error: ENOTFOUND rec.plugandwork.fr
|
|
121
|
-
```
|
|
122
|
-
→ Vérifier l'URL de base
|
|
123
|
-
|
|
124
|
-
### Limite dépassée
|
|
125
|
-
```
|
|
126
|
-
Error: Too many requests
|
|
127
|
-
```
|
|
128
|
-
→ Augmenter les délais ou réduire la limit
|
|
129
|
-
|
|
130
|
-
## Performance
|
|
131
|
-
|
|
132
|
-
Avec limit=10 :
|
|
133
|
-
- Durée : ~1-2 secondes
|
|
134
|
-
- Documents : 10 maximum
|
|
135
|
-
- Pagination : 1 page
|
|
136
|
-
|
|
137
|
-
Avec limit=100 :
|
|
138
|
-
- Durée : ~5-10 secondes
|
|
139
|
-
- Documents : 100 maximum
|
|
140
|
-
- Pagination : Multiple pages
|
|
141
|
-
|
|
142
|
-
## Paramètres utiles
|
|
143
|
-
|
|
144
|
-
- **limit** : Nombre max de documents (utile pour tests)
|
|
145
|
-
- **tempDir** : Répertoire temporaire pour les buffers
|
|
146
|
-
- **bearerToken** : Token d'auth direct (évite username/password)
|
|
147
|
-
|
|
148
|
-
## Types de documents Core observés
|
|
149
|
-
|
|
150
|
-
- **file** : Fichiers généraux
|
|
151
|
-
- **contact** : Contacts/fiches client
|
|
152
|
-
- **note** : Notes internes
|
|
153
|
-
- **email** : Emails
|
|
154
|
-
- **task** : Tâches
|
|
155
|
-
- **doc** : Documents structurés
|
package/plugins/core/index.ts
DELETED
|
@@ -1,510 +0,0 @@
|
|
|
1
|
-
import axios, { AxiosInstance } from 'axios';
|
|
2
|
-
import fs from 'fs-extra';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { ConcurrentProcessor } from '../../src/utils/concurrent-processor';
|
|
5
|
-
import {
|
|
6
|
-
DocumentSourcePlugin,
|
|
7
|
-
DocumentSource,
|
|
8
|
-
PluginConfig,
|
|
9
|
-
ScanResult,
|
|
10
|
-
PluginImportOptions,
|
|
11
|
-
PluginExportOptions,
|
|
12
|
-
ImportResult,
|
|
13
|
-
ExportResult
|
|
14
|
-
} from '../../src/types/plugins';
|
|
15
|
-
|
|
16
|
-
interface CoreConfig extends PluginConfig {
|
|
17
|
-
baseUrl: string;
|
|
18
|
-
username: string;
|
|
19
|
-
password: string;
|
|
20
|
-
bearerToken?: string;
|
|
21
|
-
tempDir?: string; // Directory for local buffer files
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface CoreDocResponse {
|
|
25
|
-
data: CoreDoc[];
|
|
26
|
-
meta?: {
|
|
27
|
-
pagination?: {
|
|
28
|
-
page?: number;
|
|
29
|
-
pages?: number;
|
|
30
|
-
count?: number;
|
|
31
|
-
total?: number;
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface CoreDoc {
|
|
37
|
-
id: string;
|
|
38
|
-
type: string;
|
|
39
|
-
attributes: {
|
|
40
|
-
title: string;
|
|
41
|
-
body?: string;
|
|
42
|
-
type?: string;
|
|
43
|
-
subtype?: string;
|
|
44
|
-
path?: string;
|
|
45
|
-
created_at?: string;
|
|
46
|
-
updated_at?: string;
|
|
47
|
-
tags?: string[];
|
|
48
|
-
percent?: number;
|
|
49
|
-
note?: number;
|
|
50
|
-
has_attachments?: boolean;
|
|
51
|
-
signed?: boolean;
|
|
52
|
-
};
|
|
53
|
-
relationships?: any;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export default class CorePlugin implements DocumentSourcePlugin {
|
|
57
|
-
readonly name = 'core';
|
|
58
|
-
readonly version = '1.0.0';
|
|
59
|
-
readonly description = 'Core document management system source';
|
|
60
|
-
readonly supportedOperations = ['import', 'both'] as const;
|
|
61
|
-
|
|
62
|
-
private config?: CoreConfig;
|
|
63
|
-
private apiClient?: AxiosInstance;
|
|
64
|
-
|
|
65
|
-
async testConnection(config: PluginConfig): Promise<boolean> {
|
|
66
|
-
try {
|
|
67
|
-
const coreConfig = config as CoreConfig;
|
|
68
|
-
const client = await this.createApiClient(coreConfig);
|
|
69
|
-
|
|
70
|
-
// Test connection by trying to fetch docs (first page only)
|
|
71
|
-
const response = await client.get('/api/d2/docs', {
|
|
72
|
-
params: {
|
|
73
|
-
'[page][number]': 1,
|
|
74
|
-
'[page][size]': 1
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
return response.status === 200;
|
|
79
|
-
} catch (error: any) {
|
|
80
|
-
console.error(`Core connection test failed: ${error.message}`);
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async scan(config: PluginConfig, options?: PluginImportOptions): Promise<ScanResult> {
|
|
86
|
-
this.config = config as CoreConfig;
|
|
87
|
-
this.apiClient = await this.createApiClient(this.config);
|
|
88
|
-
|
|
89
|
-
const sources: DocumentSource[] = [];
|
|
90
|
-
const errors: string[] = [];
|
|
91
|
-
let totalSize = 0;
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
const limit = (this.config as any).limit || (options as any)?.limit;
|
|
95
|
-
console.log(`🔍 Scanning Core documents${limit ? ` (limit: ${limit})` : ''}...`);
|
|
96
|
-
|
|
97
|
-
// Scan all pages of documents
|
|
98
|
-
let currentPage = 1;
|
|
99
|
-
let hasMorePages = true;
|
|
100
|
-
const pageSize = limit && limit < 100 ? limit : 100; // Use limit if smaller than default batch
|
|
101
|
-
|
|
102
|
-
while (hasMorePages && (!limit || sources.length < limit)) {
|
|
103
|
-
try {
|
|
104
|
-
const response = await this.apiClient.get<CoreDocResponse>('/api/d2/docs', {
|
|
105
|
-
params: {
|
|
106
|
-
'[page][number]': currentPage,
|
|
107
|
-
'[page][size]': pageSize,
|
|
108
|
-
// Add filters if provided in options
|
|
109
|
-
...(options?.filters?.path && { path: options.filters.path }),
|
|
110
|
-
...(options?.filters?.mimeTypes && { type: options.filters.mimeTypes.join(',') })
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
const docs = response.data.data || [];
|
|
115
|
-
console.log(`📄 Processing page ${currentPage}: ${docs.length} documents`);
|
|
116
|
-
|
|
117
|
-
for (const doc of docs) {
|
|
118
|
-
if (doc.attributes && doc.attributes.title) {
|
|
119
|
-
const source: DocumentSource = {
|
|
120
|
-
id: doc.id,
|
|
121
|
-
name: doc.attributes.title,
|
|
122
|
-
path: this.buildDocumentPath(doc),
|
|
123
|
-
size: this.estimateDocumentSize(doc),
|
|
124
|
-
mimeType: this.determineMimeType(doc),
|
|
125
|
-
lastModified: new Date(doc.attributes.updated_at || doc.attributes.created_at || Date.now()),
|
|
126
|
-
metadata: {
|
|
127
|
-
coreId: doc.id,
|
|
128
|
-
coreType: doc.attributes.type,
|
|
129
|
-
coreSubtype: doc.attributes.subtype,
|
|
130
|
-
corePath: doc.attributes.path,
|
|
131
|
-
tags: doc.attributes.tags,
|
|
132
|
-
hasAttachments: doc.attributes.has_attachments,
|
|
133
|
-
signed: doc.attributes.signed,
|
|
134
|
-
percent: doc.attributes.percent,
|
|
135
|
-
note: doc.attributes.note,
|
|
136
|
-
body: doc.attributes.body?.substring(0, 500) + '...' // Truncate body for metadata
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
// Apply filters
|
|
141
|
-
if (this.shouldIncludeSource(source, options)) {
|
|
142
|
-
sources.push(source);
|
|
143
|
-
totalSize += source.size;
|
|
144
|
-
|
|
145
|
-
// Stop if we've reached the limit
|
|
146
|
-
if (limit && sources.length >= limit) {
|
|
147
|
-
console.log(`📏 Reached limit of ${limit} documents`);
|
|
148
|
-
break;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Check for more pages
|
|
155
|
-
const pagination = response.data.meta?.pagination;
|
|
156
|
-
if (pagination && pagination.page && pagination.pages) {
|
|
157
|
-
hasMorePages = pagination.page < pagination.pages;
|
|
158
|
-
currentPage++;
|
|
159
|
-
} else {
|
|
160
|
-
// Fallback: if no docs returned, assume we've reached the end
|
|
161
|
-
hasMorePages = docs.length === pageSize;
|
|
162
|
-
if (hasMorePages) currentPage++;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Add small delay to respect API limits
|
|
166
|
-
await this.sleep(100);
|
|
167
|
-
|
|
168
|
-
} catch (pageError: any) {
|
|
169
|
-
console.warn(`⚠️ Warning: Failed to fetch page ${currentPage}: ${pageError.message}`);
|
|
170
|
-
errors.push(`Failed to fetch page ${currentPage}: ${pageError.message}`);
|
|
171
|
-
break;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
console.log(`✅ Scan completed: ${sources.length} documents found`);
|
|
176
|
-
|
|
177
|
-
return {
|
|
178
|
-
sources,
|
|
179
|
-
totalCount: sources.length,
|
|
180
|
-
totalSize,
|
|
181
|
-
errors
|
|
182
|
-
};
|
|
183
|
-
} catch (error: any) {
|
|
184
|
-
return {
|
|
185
|
-
sources: [],
|
|
186
|
-
totalCount: 0,
|
|
187
|
-
totalSize: 0,
|
|
188
|
-
errors: [`Core scan failed: ${error.message}`]
|
|
189
|
-
};
|
|
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 CoreConfig;
|
|
200
|
-
this.apiClient = await this.createApiClient(this.config);
|
|
201
|
-
|
|
202
|
-
const concurrency = options?.concurrent || 1;
|
|
203
|
-
const tempDir = this.config.tempDir || path.join(targetDir, '.temp');
|
|
204
|
-
|
|
205
|
-
// Ensure temp directory exists
|
|
206
|
-
await fs.ensureDir(tempDir);
|
|
207
|
-
|
|
208
|
-
console.log(`📥 Starting concurrent import of ${sources.length} documents from Core (${concurrency} jobs)`);
|
|
209
|
-
|
|
210
|
-
// Use concurrent processor
|
|
211
|
-
const processingResult = await ConcurrentProcessor.processWithProgress(
|
|
212
|
-
sources,
|
|
213
|
-
async (source: DocumentSource, index: number) => {
|
|
214
|
-
const result = await this.importSingle(source, targetDir, tempDir);
|
|
215
|
-
|
|
216
|
-
// Rate limiting delay (only for concurrent > 1)
|
|
217
|
-
if (concurrency > 1) {
|
|
218
|
-
await this.sleep(100); // Reduced delay for concurrent operations
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return result;
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
concurrency,
|
|
225
|
-
operation: 'Import from Core',
|
|
226
|
-
itemName: 'documents'
|
|
227
|
-
}
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
// Cleanup temp directory
|
|
231
|
-
try {
|
|
232
|
-
await fs.remove(tempDir);
|
|
233
|
-
console.log('🧹 Temporary files cleaned up');
|
|
234
|
-
} catch (cleanupError) {
|
|
235
|
-
console.warn('⚠️ Warning: Failed to cleanup temporary directory');
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Filter valid results and handle errors
|
|
239
|
-
const validResults = processingResult.results.filter(r => r !== undefined);
|
|
240
|
-
const errorResults: ImportResult[] = processingResult.errors.map(({ item, error }) => ({
|
|
241
|
-
success: false,
|
|
242
|
-
source: item,
|
|
243
|
-
error: error.message
|
|
244
|
-
}));
|
|
245
|
-
|
|
246
|
-
return [...validResults, ...errorResults];
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
private async importSingle(source: DocumentSource, targetDir: string, tempDir: string): Promise<ImportResult> {
|
|
250
|
-
try {
|
|
251
|
-
if (!this.apiClient) throw new Error('API client not initialized');
|
|
252
|
-
|
|
253
|
-
console.log(`📄 Importing: ${source.name}`);
|
|
254
|
-
|
|
255
|
-
// Step 1: Fetch document details and content from Core
|
|
256
|
-
const docResponse = await this.apiClient.get(`/api/d2/docs/${source.id}`);
|
|
257
|
-
const docData = docResponse.data.data;
|
|
258
|
-
|
|
259
|
-
if (!docData) {
|
|
260
|
-
throw new Error('Document not found in Core');
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Step 2: Create local buffer file
|
|
264
|
-
const tempFileName = `${source.id}_${source.name.replace(/[^a-zA-Z0-9.-]/g, '_')}`;
|
|
265
|
-
const tempFilePath = path.join(tempDir, tempFileName);
|
|
266
|
-
|
|
267
|
-
// Step 3: Generate document content (Core stores content in body field)
|
|
268
|
-
let content = '';
|
|
269
|
-
|
|
270
|
-
if (docData.attributes.body) {
|
|
271
|
-
content = docData.attributes.body;
|
|
272
|
-
} else {
|
|
273
|
-
// Fallback: create a minimal document with metadata
|
|
274
|
-
content = `Document: ${source.name}\n\n`;
|
|
275
|
-
content += `Type: ${source.metadata?.coreType || 'unknown'}\n`;
|
|
276
|
-
content += `Subtype: ${source.metadata?.coreSubtype || 'unknown'}\n`;
|
|
277
|
-
content += `Created: ${source.lastModified.toISOString()}\n`;
|
|
278
|
-
if (source.metadata?.tags && Array.isArray(source.metadata.tags)) {
|
|
279
|
-
content += `Tags: ${source.metadata.tags.join(', ')}\n`;
|
|
280
|
-
}
|
|
281
|
-
content += `\nImported from Core (ID: ${source.id})`;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Step 4: Write content to temporary file
|
|
285
|
-
await fs.writeFile(tempFilePath, content, 'utf8');
|
|
286
|
-
|
|
287
|
-
// Step 5: Move to target directory with proper structure
|
|
288
|
-
const targetPath = path.join(targetDir, source.path);
|
|
289
|
-
const targetDirectory = path.dirname(targetPath);
|
|
290
|
-
|
|
291
|
-
await fs.ensureDir(targetDirectory);
|
|
292
|
-
await fs.move(tempFilePath, targetPath, { overwrite: true });
|
|
293
|
-
|
|
294
|
-
const stats = await fs.stat(targetPath);
|
|
295
|
-
|
|
296
|
-
console.log(`✅ Imported: ${source.name} (${stats.size} bytes)`);
|
|
297
|
-
|
|
298
|
-
return {
|
|
299
|
-
success: true,
|
|
300
|
-
source,
|
|
301
|
-
localPath: targetPath,
|
|
302
|
-
bytesTransferred: stats.size
|
|
303
|
-
};
|
|
304
|
-
} catch (error: any) {
|
|
305
|
-
console.error(`❌ Failed to import ${source.name}: ${error.message}`);
|
|
306
|
-
return {
|
|
307
|
-
success: false,
|
|
308
|
-
source,
|
|
309
|
-
error: error.message
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Export is not supported for Core (documents are managed within Core)
|
|
315
|
-
async export?(
|
|
316
|
-
config: PluginConfig,
|
|
317
|
-
localSources: DocumentSource[],
|
|
318
|
-
options?: PluginExportOptions
|
|
319
|
-
): Promise<ExportResult[]> {
|
|
320
|
-
throw new Error('Export to Core is not supported. Core documents should be managed within the Core system.');
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
getConfigSchema(): Record<string, any> {
|
|
324
|
-
return {
|
|
325
|
-
type: 'object',
|
|
326
|
-
properties: {
|
|
327
|
-
baseUrl: {
|
|
328
|
-
type: 'string',
|
|
329
|
-
description: 'Core API base URL (e.g., https://your-core-instance.com)',
|
|
330
|
-
required: true
|
|
331
|
-
},
|
|
332
|
-
username: {
|
|
333
|
-
type: 'string',
|
|
334
|
-
description: 'Core username for authentication',
|
|
335
|
-
required: true
|
|
336
|
-
},
|
|
337
|
-
password: {
|
|
338
|
-
type: 'string',
|
|
339
|
-
description: 'Core password for authentication',
|
|
340
|
-
required: true
|
|
341
|
-
},
|
|
342
|
-
bearerToken: {
|
|
343
|
-
type: 'string',
|
|
344
|
-
description: 'Pre-obtained bearer token (optional, will be fetched if not provided)',
|
|
345
|
-
required: false
|
|
346
|
-
},
|
|
347
|
-
tempDir: {
|
|
348
|
-
type: 'string',
|
|
349
|
-
description: 'Temporary directory for local buffer files (optional)',
|
|
350
|
-
required: false
|
|
351
|
-
},
|
|
352
|
-
limit: {
|
|
353
|
-
type: 'integer',
|
|
354
|
-
description: 'Maximum number of documents to scan (useful for testing)',
|
|
355
|
-
required: false
|
|
356
|
-
}
|
|
357
|
-
},
|
|
358
|
-
required: ['baseUrl', 'username', 'password']
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
async initialize(config: PluginConfig): Promise<void> {
|
|
363
|
-
this.config = config as CoreConfig;
|
|
364
|
-
|
|
365
|
-
if (!this.config.baseUrl) {
|
|
366
|
-
throw new Error('Core plugin requires baseUrl');
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
if (!this.config.bearerToken && (!this.config.username || !this.config.password)) {
|
|
370
|
-
throw new Error('Core plugin requires either bearerToken or username/password');
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Test authentication
|
|
374
|
-
try {
|
|
375
|
-
this.apiClient = await this.createApiClient(this.config);
|
|
376
|
-
console.log('✅ Core plugin initialized successfully');
|
|
377
|
-
} catch (error: any) {
|
|
378
|
-
throw new Error(`Failed to initialize Core plugin: ${error.message}`);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
async destroy(): Promise<void> {
|
|
383
|
-
this.config = undefined;
|
|
384
|
-
this.apiClient = undefined;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
private async createApiClient(config: CoreConfig): Promise<AxiosInstance> {
|
|
388
|
-
let bearerToken = config.bearerToken;
|
|
389
|
-
|
|
390
|
-
// If no bearer token provided, authenticate to get one
|
|
391
|
-
if (!bearerToken && config.username && config.password) {
|
|
392
|
-
console.log('🔐 No bearer token provided, authenticating...');
|
|
393
|
-
bearerToken = await this.authenticate(config);
|
|
394
|
-
} else if (!bearerToken) {
|
|
395
|
-
throw new Error('Either bearerToken or username/password must be provided');
|
|
396
|
-
} else {
|
|
397
|
-
console.log('🔑 Using provided bearer token');
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
return axios.create({
|
|
401
|
-
baseURL: config.baseUrl,
|
|
402
|
-
headers: {
|
|
403
|
-
'Authorization': `Bearer ${bearerToken}`,
|
|
404
|
-
'Content-Type': 'application/json'
|
|
405
|
-
},
|
|
406
|
-
timeout: 30000
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
private async authenticate(config: CoreConfig): Promise<string> {
|
|
411
|
-
try {
|
|
412
|
-
console.log('🔐 Authenticating with Core...');
|
|
413
|
-
|
|
414
|
-
const authClient = axios.create({
|
|
415
|
-
baseURL: config.baseUrl,
|
|
416
|
-
timeout: 30000
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
// Use the token endpoint from the swagger
|
|
420
|
-
const response = await authClient.post('/api/token', null, {
|
|
421
|
-
params: {
|
|
422
|
-
'auth[username]': config.username,
|
|
423
|
-
'auth[password]': config.password,
|
|
424
|
-
'grant_type': 'password'
|
|
425
|
-
}
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
if (response.data && response.data.access_token) {
|
|
429
|
-
console.log('✅ Core authentication successful');
|
|
430
|
-
return response.data.access_token;
|
|
431
|
-
} else {
|
|
432
|
-
throw new Error('No access token received from Core API');
|
|
433
|
-
}
|
|
434
|
-
} catch (error: any) {
|
|
435
|
-
throw new Error(`Core authentication failed: ${error.response?.data?.message || error.message}`);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
private buildDocumentPath(doc: CoreDoc): string {
|
|
440
|
-
// Build a meaningful path structure
|
|
441
|
-
const type = doc.attributes.type || 'unknown';
|
|
442
|
-
const subtype = doc.attributes.subtype || 'general';
|
|
443
|
-
const title = doc.attributes.title || doc.id;
|
|
444
|
-
|
|
445
|
-
// Clean title for filesystem
|
|
446
|
-
const cleanTitle = title.replace(/[^a-zA-Z0-9.-]/g, '_');
|
|
447
|
-
|
|
448
|
-
return `${type}/${subtype}/${cleanTitle}.txt`;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
private estimateDocumentSize(doc: CoreDoc): number {
|
|
452
|
-
// Estimate size based on content
|
|
453
|
-
let size = 0;
|
|
454
|
-
|
|
455
|
-
if (doc.attributes.body) {
|
|
456
|
-
size += doc.attributes.body.length;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
if (doc.attributes.title) {
|
|
460
|
-
size += doc.attributes.title.length;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// Add base size for metadata
|
|
464
|
-
size += 500;
|
|
465
|
-
|
|
466
|
-
return Math.max(size, 100); // Minimum 100 bytes
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
private determineMimeType(doc: CoreDoc): string {
|
|
470
|
-
const type = doc.attributes.type?.toLowerCase();
|
|
471
|
-
const subtype = doc.attributes.subtype?.toLowerCase();
|
|
472
|
-
|
|
473
|
-
// Map Core types to MIME types
|
|
474
|
-
if (type === 'document' || subtype === 'document') {
|
|
475
|
-
return 'text/plain';
|
|
476
|
-
} else if (type === 'html' || subtype === 'html') {
|
|
477
|
-
return 'text/html';
|
|
478
|
-
} else if (doc.attributes.has_attachments) {
|
|
479
|
-
return 'application/octet-stream';
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// Default to plain text
|
|
483
|
-
return 'text/plain';
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
private shouldIncludeSource(source: DocumentSource, options?: PluginImportOptions): boolean {
|
|
487
|
-
// Apply size filter
|
|
488
|
-
if (options?.filters?.maxSize && source.size > options.filters.maxSize) {
|
|
489
|
-
return false;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// Apply date range filter
|
|
493
|
-
if (options?.filters?.dateRange) {
|
|
494
|
-
const { from, to } = options.filters.dateRange;
|
|
495
|
-
if (from && source.lastModified < from) return false;
|
|
496
|
-
if (to && source.lastModified > to) return false;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// Apply MIME type filter
|
|
500
|
-
if (options?.filters?.mimeTypes && !options.filters.mimeTypes.includes(source.mimeType)) {
|
|
501
|
-
return false;
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
return true;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
private sleep(ms: number): Promise<void> {
|
|
508
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
509
|
-
}
|
|
510
|
-
}
|
package/plugins/core/plugin.json
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "core",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "Core document management system source plugin",
|
|
5
|
-
"author": "HubDoc Tools",
|
|
6
|
-
"main": "index.ts",
|
|
7
|
-
"dependencies": {
|
|
8
|
-
"axios": "^1.5.0",
|
|
9
|
-
"fs-extra": "^11.1.0"
|
|
10
|
-
},
|
|
11
|
-
"hubdocToolVersion": "^1.0.0",
|
|
12
|
-
"capabilities": {
|
|
13
|
-
"scan": true,
|
|
14
|
-
"import": true,
|
|
15
|
-
"export": false
|
|
16
|
-
},
|
|
17
|
-
"apiEndpoints": {
|
|
18
|
-
"authentication": "/api/token",
|
|
19
|
-
"documents": "/api/d2/docs",
|
|
20
|
-
"documentDetail": "/api/d2/docs/{id}"
|
|
21
|
-
},
|
|
22
|
-
"configuration": {
|
|
23
|
-
"required": ["baseUrl", "username", "password"],
|
|
24
|
-
"optional": ["bearerToken", "tempDir"]
|
|
25
|
-
}
|
|
26
|
-
}
|