plugin-file-preview-auth 1.3.2 → 1.3.3
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/client/AIFilePreviewAction.d.ts +5 -0
- package/dist/client/index.js +1 -1
- package/dist/externalVersion.js +3 -1
- package/dist/server/collections/attachment-ocr-results.d.ts +2 -0
- package/dist/server/collections/{attachments.js → attachment-ocr-results.js} +35 -10
- package/dist/server/migrations/20260528000000-move-ocr-fields-out-of-attachments.d.ts +5 -0
- package/dist/server/migrations/20260528000000-move-ocr-fields-out-of-attachments.js +65 -0
- package/dist/server/ocr/tesseract-worker.d.ts +1 -0
- package/dist/server/ocr/tesseract-worker.js +46 -15
- package/dist/server/plugin.d.ts +11 -0
- package/dist/server/plugin.js +523 -47
- package/package.json +2 -1
- package/dist/server/collections/attachments.d.ts +0 -13
package/dist/server/plugin.js
CHANGED
|
@@ -46,6 +46,7 @@ var import_excel_parser_handler = require("./excel-parser-handler");
|
|
|
46
46
|
var import_promises = require("fs/promises");
|
|
47
47
|
var import_os = __toESM(require("os"));
|
|
48
48
|
var import_path = __toESM(require("path"));
|
|
49
|
+
var import_sequelize = require("sequelize");
|
|
49
50
|
var import_tesseract_worker = require("./ocr/tesseract-worker");
|
|
50
51
|
const FILE_PREVIEW_WORK_CONTEXT_TYPE = "file-preview";
|
|
51
52
|
const MAX_AI_CONTEXT_CHARS = 5e4;
|
|
@@ -58,6 +59,7 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
|
|
|
58
59
|
await this.db.import({ directory: import_path.default.resolve(__dirname, "collections") });
|
|
59
60
|
}
|
|
60
61
|
async load() {
|
|
62
|
+
await this.syncOcrResultCollection();
|
|
61
63
|
this.cache = await this.app.cacheManager.createCache({ name: "file-preview-auth" });
|
|
62
64
|
this.ocrWorker = new import_tesseract_worker.TesseractWorker(this.app);
|
|
63
65
|
this.registerExcelParser();
|
|
@@ -83,6 +85,12 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
|
|
|
83
85
|
this.ocrWorker.stop();
|
|
84
86
|
}
|
|
85
87
|
}
|
|
88
|
+
async syncOcrResultCollection() {
|
|
89
|
+
const collection = this.db.getCollection("attachmentOcrResults");
|
|
90
|
+
if (collection) {
|
|
91
|
+
await collection.sync();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
86
94
|
/**
|
|
87
95
|
* Disable NocoBase's built-in Office previewer after plugins have loaded.
|
|
88
96
|
* This keeps this authenticated previewer as the active Office handler without causing a restart loop.
|
|
@@ -132,6 +140,7 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
|
|
|
132
140
|
await next();
|
|
133
141
|
},
|
|
134
142
|
download: async (ctx, next) => {
|
|
143
|
+
var _a, _b, _c, _d;
|
|
135
144
|
const params = ctx.action.params || {};
|
|
136
145
|
const values = params.values || {};
|
|
137
146
|
const reqQuery = ctx.request.query || {};
|
|
@@ -159,7 +168,7 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
|
|
|
159
168
|
const collection = values.collection || values.collectionName || params.collection || params.collectionName || reqQuery.collection || reqQuery.collectionName || reqBody.collection || reqBody.collectionName || fileInput.collectionName;
|
|
160
169
|
const storageIdInput = values.storageId || params.storageId || reqQuery.storageId || reqBody.storageId || fileInput.storageId;
|
|
161
170
|
let storageId = storageIdInput;
|
|
162
|
-
const fileManager = this.pm.get("@nocobase/plugin-file-manager");
|
|
171
|
+
const fileManager = this.pm.get("@nocobase/plugin-file-manager") || this.pm.get("file-manager");
|
|
163
172
|
if (!fileManager) {
|
|
164
173
|
ctx.throw(500, "File manager plugin not found");
|
|
165
174
|
}
|
|
@@ -173,8 +182,22 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
|
|
|
173
182
|
} catch (e) {
|
|
174
183
|
}
|
|
175
184
|
let attachment = null;
|
|
185
|
+
let attachmentCollection = collection;
|
|
176
186
|
let isVirtual = false;
|
|
177
187
|
const collectionsToTry = Array.from(new Set([collection, "aiFiles", "attachments"].filter(Boolean)));
|
|
188
|
+
this.log.debug(
|
|
189
|
+
`[FilePreviewAuth] Download input ${safeDebugJson({
|
|
190
|
+
requestedId: toDebugValue(requestedId),
|
|
191
|
+
collection,
|
|
192
|
+
hasRawUrl: Boolean(rawUrl),
|
|
193
|
+
urlPath: getDebugUrlPath(url),
|
|
194
|
+
filterByTk: toDebugValue(filterByTk),
|
|
195
|
+
storageIdInput: toDebugValue(storageIdInput),
|
|
196
|
+
parsedStorageId: toDebugValue(storageId),
|
|
197
|
+
fileInputKeys: getObjectKeys(fileInput),
|
|
198
|
+
storageCache: summarizeStorageCache(fileManager.storagesCache)
|
|
199
|
+
})}`
|
|
200
|
+
);
|
|
178
201
|
if (requestedId || (fileInput == null ? void 0 : fileInput.id) || (fileInput == null ? void 0 : fileInput.uid)) {
|
|
179
202
|
try {
|
|
180
203
|
attachment = await this.resolveAttachment(ctx, {
|
|
@@ -189,6 +212,7 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
|
|
|
189
212
|
filename: values.filename || params.filename || reqQuery.filename || reqBody.filename || fileInput.filename,
|
|
190
213
|
mimetype: values.mimetype || params.mimetype || reqQuery.mimetype || reqBody.mimetype || fileInput.mimetype
|
|
191
214
|
});
|
|
215
|
+
attachmentCollection = ((_b = (_a = attachment == null ? void 0 : attachment.constructor) == null ? void 0 : _a.collection) == null ? void 0 : _b.name) || collection || fileInput.collectionName || attachmentCollection;
|
|
192
216
|
} catch {
|
|
193
217
|
attachment = null;
|
|
194
218
|
}
|
|
@@ -205,6 +229,7 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
|
|
|
205
229
|
filename: values.filename || params.filename || reqQuery.filename || reqBody.filename || fileInput.filename,
|
|
206
230
|
mimetype: values.mimetype || params.mimetype || reqQuery.mimetype || reqBody.mimetype || fileInput.mimetype
|
|
207
231
|
});
|
|
232
|
+
attachmentCollection = ((_d = (_c = attachment == null ? void 0 : attachment.constructor) == null ? void 0 : _c.collection) == null ? void 0 : _d.name) || collection || fileInput.collectionName || attachmentCollection;
|
|
208
233
|
} catch {
|
|
209
234
|
attachment = null;
|
|
210
235
|
}
|
|
@@ -223,6 +248,7 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
|
|
|
223
248
|
attachment = await repo.findOne({ filter: { url } });
|
|
224
249
|
}
|
|
225
250
|
if (attachment) {
|
|
251
|
+
attachmentCollection = colName;
|
|
226
252
|
break;
|
|
227
253
|
}
|
|
228
254
|
}
|
|
@@ -233,6 +259,7 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
|
|
|
233
259
|
...fileInput,
|
|
234
260
|
url,
|
|
235
261
|
storageId,
|
|
262
|
+
collectionName: collection || fileInput.collectionName,
|
|
236
263
|
filename: values.filename || params.filename || reqQuery.filename || reqBody.filename || fileInput.filename || "file",
|
|
237
264
|
mimetype: values.mimetype || params.mimetype || reqQuery.mimetype || reqBody.mimetype || fileInput.mimetype
|
|
238
265
|
};
|
|
@@ -241,26 +268,30 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
|
|
|
241
268
|
if (!attachment) {
|
|
242
269
|
ctx.throw(404, "Attachment not found for this URL");
|
|
243
270
|
}
|
|
271
|
+
this.log.debug(
|
|
272
|
+
`[FilePreviewAuth] Attachment resolved ${safeDebugJson({
|
|
273
|
+
...summarizeAttachmentForLog(attachment, attachmentCollection),
|
|
274
|
+
isVirtual,
|
|
275
|
+
requestedCollection: collection
|
|
276
|
+
})}`
|
|
277
|
+
);
|
|
244
278
|
if (!isVirtual) {
|
|
245
279
|
await this.assertCanAccessAttachment(ctx, attachment);
|
|
246
280
|
}
|
|
247
281
|
try {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (String(key) === strId) {
|
|
254
|
-
matchedKey = key;
|
|
255
|
-
break;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
const attachmentObj = typeof attachment.toJSON === "function" ? attachment.toJSON() : { ...attachment };
|
|
260
|
-
if (matchedKey !== null) {
|
|
261
|
-
attachmentObj.storageId = matchedKey;
|
|
262
|
-
}
|
|
282
|
+
const attachmentObj = await this.prepareAttachmentForFileManager(
|
|
283
|
+
attachment,
|
|
284
|
+
fileManager,
|
|
285
|
+
attachmentCollection
|
|
286
|
+
);
|
|
263
287
|
const storageModel = getStorageFromCache(fileManager.storagesCache, attachmentObj.storageId);
|
|
288
|
+
this.log.debug(
|
|
289
|
+
`[FilePreviewAuth] Attachment prepared for stream ${safeDebugJson({
|
|
290
|
+
...summarizeAttachmentForLog(attachmentObj, attachmentCollection),
|
|
291
|
+
storageModel: summarizeStorageForLog(storageModel),
|
|
292
|
+
storageCache: summarizeStorageCache(fileManager.storagesCache)
|
|
293
|
+
})}`
|
|
294
|
+
);
|
|
264
295
|
if (storageModel && (storageModel.type === "s3" || storageModel.type === "aws-s3" || storageModel.type === "s3-private")) {
|
|
265
296
|
const StorageTypeClass = fileManager.storageTypes.get(storageModel.type);
|
|
266
297
|
const storageInstance = new StorageTypeClass(storageModel);
|
|
@@ -305,19 +336,107 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
|
|
|
305
336
|
ctx.throw(404, "Attachment not found");
|
|
306
337
|
}
|
|
307
338
|
await this.assertCanAccessAttachment(ctx, attachment);
|
|
308
|
-
await
|
|
309
|
-
|
|
310
|
-
|
|
339
|
+
const ocrRecord = await this.upsertOcrResult(attachmentId, {
|
|
340
|
+
status: "pending-ocr",
|
|
341
|
+
error: null
|
|
311
342
|
});
|
|
312
343
|
await this.ocrWorker.enqueue(attachmentId);
|
|
313
|
-
ctx.body = {
|
|
344
|
+
ctx.body = {
|
|
345
|
+
ok: true,
|
|
346
|
+
data: this.serializeOcrResult(ocrRecord, attachmentId)
|
|
347
|
+
};
|
|
348
|
+
await next();
|
|
349
|
+
},
|
|
350
|
+
getOcrStatus: async (ctx, next) => {
|
|
351
|
+
const params = ctx.action.params || {};
|
|
352
|
+
const reqQuery = ctx.request.query || {};
|
|
353
|
+
const reqBody = ctx.request.body || {};
|
|
354
|
+
const values = params.values || {};
|
|
355
|
+
const attachmentId = values.attachmentId || params.attachmentId || reqQuery.attachmentId || reqBody.attachmentId;
|
|
356
|
+
if (!attachmentId) {
|
|
357
|
+
ctx.throw(400, "attachmentId is required");
|
|
358
|
+
}
|
|
359
|
+
this.assertAuthenticated(ctx);
|
|
360
|
+
const repo = ctx.db.getRepository("attachments");
|
|
361
|
+
const attachment = await repo.findOne({ filterByTk: attachmentId });
|
|
362
|
+
if (!attachment) {
|
|
363
|
+
ctx.throw(404, "Attachment not found");
|
|
364
|
+
}
|
|
365
|
+
await this.assertCanAccessAttachment(ctx, attachment);
|
|
366
|
+
const ocrRecord = await this.getOcrResultByAttachmentId(attachmentId);
|
|
367
|
+
ctx.body = {
|
|
368
|
+
data: this.serializeOcrResult(ocrRecord, attachmentId)
|
|
369
|
+
};
|
|
314
370
|
await next();
|
|
315
371
|
}
|
|
316
372
|
}
|
|
317
373
|
});
|
|
318
|
-
this.app.acl.allow("filePreviewAuth", ["download", "getContent", "runOcr"], "loggedIn");
|
|
374
|
+
this.app.acl.allow("filePreviewAuth", ["download", "getContent", "runOcr", "getOcrStatus"], "loggedIn");
|
|
375
|
+
this.app.acl.allow("attachmentOcrResults", ["get", "list", "create", "update"], "loggedIn");
|
|
319
376
|
this.log.info("[FilePreviewAuth] Registered /api/filePreviewAuth:download & runOcr endpoints");
|
|
320
377
|
}
|
|
378
|
+
async getOcrResultByAttachmentId(attachmentId) {
|
|
379
|
+
const repo = this.db.getRepository("attachmentOcrResults");
|
|
380
|
+
if (!repo) {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
return repo.findOne({
|
|
384
|
+
filter: {
|
|
385
|
+
attachmentId
|
|
386
|
+
},
|
|
387
|
+
appends: ["attachment"]
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
async upsertOcrResult(attachmentId, values) {
|
|
391
|
+
const repo = this.db.getRepository("attachmentOcrResults");
|
|
392
|
+
if (!repo) {
|
|
393
|
+
throw new Error("attachmentOcrResults repository not found");
|
|
394
|
+
}
|
|
395
|
+
const existing = await repo.findOne({
|
|
396
|
+
filter: {
|
|
397
|
+
attachmentId
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
const nextValues = {
|
|
401
|
+
attachmentId,
|
|
402
|
+
...values
|
|
403
|
+
};
|
|
404
|
+
if (existing) {
|
|
405
|
+
await repo.update({
|
|
406
|
+
filterByTk: existing.get("id"),
|
|
407
|
+
values: nextValues
|
|
408
|
+
});
|
|
409
|
+
return repo.findOne({
|
|
410
|
+
filterByTk: existing.get("id"),
|
|
411
|
+
appends: ["attachment"]
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
const created = await repo.create({
|
|
415
|
+
values: nextValues
|
|
416
|
+
});
|
|
417
|
+
return repo.findOne({
|
|
418
|
+
filterByTk: created.get("id"),
|
|
419
|
+
appends: ["attachment"]
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
serializeOcrResult(record, attachmentId) {
|
|
423
|
+
if (!record) {
|
|
424
|
+
return {
|
|
425
|
+
attachmentId,
|
|
426
|
+
status: "no-ocr",
|
|
427
|
+
data: null,
|
|
428
|
+
error: null
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
const json = typeof record.toJSON === "function" ? record.toJSON() : record;
|
|
432
|
+
return {
|
|
433
|
+
id: json.id,
|
|
434
|
+
attachmentId: json.attachmentId ?? attachmentId,
|
|
435
|
+
status: json.status || "no-ocr",
|
|
436
|
+
data: json.data || null,
|
|
437
|
+
error: json.error || null
|
|
438
|
+
};
|
|
439
|
+
}
|
|
321
440
|
registerAIWorkContext() {
|
|
322
441
|
var _a;
|
|
323
442
|
let aiPlugin;
|
|
@@ -349,11 +468,7 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
|
|
|
349
468
|
async resolveAttachment(ctx, input) {
|
|
350
469
|
var _a;
|
|
351
470
|
const file = (input == null ? void 0 : input.file) || input || {};
|
|
352
|
-
const collectionNames = [
|
|
353
|
-
file.collectionName,
|
|
354
|
-
"attachments",
|
|
355
|
-
"aiFiles"
|
|
356
|
-
].filter(Boolean);
|
|
471
|
+
const collectionNames = [file.collectionName, "attachments", "aiFiles"].filter(Boolean);
|
|
357
472
|
const ids = [file.id, file.uid].filter((value) => isLikelyRecordId(value));
|
|
358
473
|
for (const collectionName of collectionNames) {
|
|
359
474
|
if (!ctx.db.getCollection(collectionName)) continue;
|
|
@@ -372,6 +487,22 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
|
|
|
372
487
|
if (record) return record;
|
|
373
488
|
}
|
|
374
489
|
}
|
|
490
|
+
for (const collectionName of collectionNames) {
|
|
491
|
+
if (!ctx.db.getCollection(collectionName)) continue;
|
|
492
|
+
const repo = ctx.db.getRepository(collectionName);
|
|
493
|
+
for (const urlCandidate of urlCandidates) {
|
|
494
|
+
const cleanUrl = urlCandidate.split("?")[0];
|
|
495
|
+
const filename = import_path.default.basename(cleanUrl);
|
|
496
|
+
if (filename && filename !== "file" && filename.includes(".")) {
|
|
497
|
+
const filter = { filename };
|
|
498
|
+
if (file.storageId) {
|
|
499
|
+
filter.storageId = file.storageId;
|
|
500
|
+
}
|
|
501
|
+
const record = await repo.findOne({ filter });
|
|
502
|
+
if (record) return record;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
375
506
|
if ((file.storageId || ((_a = file.url) == null ? void 0 : _a.includes("extStorage:download"))) && (file.url || file.path || file.key)) {
|
|
376
507
|
return file;
|
|
377
508
|
}
|
|
@@ -433,10 +564,7 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
|
|
|
433
564
|
const pk = ((_c = parentCollection == null ? void 0 : parentCollection.model) == null ? void 0 : _c.primaryKeyAttribute) || "id";
|
|
434
565
|
const count = await parentCollection.repository.count({
|
|
435
566
|
filter: {
|
|
436
|
-
$and: [
|
|
437
|
-
{ [pk]: { $in: parentIds } },
|
|
438
|
-
dataScopeFilter
|
|
439
|
-
]
|
|
567
|
+
$and: [{ [pk]: { $in: parentIds } }, dataScopeFilter]
|
|
440
568
|
}
|
|
441
569
|
});
|
|
442
570
|
if (count > 0) return true;
|
|
@@ -456,7 +584,9 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
|
|
|
456
584
|
const currentRoles = ctx.state.currentRoles || [];
|
|
457
585
|
const userRoles = currentUser.roles || [];
|
|
458
586
|
const isOwner = createdById != null && String(createdById) === String(currentUser.id);
|
|
459
|
-
const isAdmin = currentRoles.includes("root") || currentRoles.includes("admin") || userRoles.some(
|
|
587
|
+
const isAdmin = currentRoles.includes("root") || currentRoles.includes("admin") || userRoles.some(
|
|
588
|
+
(role) => role === "root" || role === "admin" || (role == null ? void 0 : role.name) === "root" || (role == null ? void 0 : role.name) === "admin"
|
|
589
|
+
);
|
|
460
590
|
if (isOwner || isAdmin) {
|
|
461
591
|
return;
|
|
462
592
|
}
|
|
@@ -544,7 +674,9 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
|
|
|
544
674
|
this.log.warn(`[FilePreviewAuth] MarkItDown parser failed: ${err}`);
|
|
545
675
|
}
|
|
546
676
|
} else {
|
|
547
|
-
this.log.warn(
|
|
677
|
+
this.log.warn(
|
|
678
|
+
"[FilePreviewAuth] plugin-markitdown-parser not found; uploaded raw text parsing fallback will be used"
|
|
679
|
+
);
|
|
548
680
|
}
|
|
549
681
|
if (isPlainTextAttachment(attachment)) {
|
|
550
682
|
return buffer.toString("utf-8");
|
|
@@ -588,21 +720,7 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
|
|
|
588
720
|
if (!(fileManager == null ? void 0 : fileManager.getFileStream)) {
|
|
589
721
|
return "";
|
|
590
722
|
}
|
|
591
|
-
|
|
592
|
-
const rawStorageId = attachment.storageId || getAttachmentValue(attachment, "storageId");
|
|
593
|
-
if (rawStorageId) {
|
|
594
|
-
const strId = String(rawStorageId);
|
|
595
|
-
for (const key of fileManager.storagesCache.keys()) {
|
|
596
|
-
if (String(key) === strId) {
|
|
597
|
-
matchedKey = key;
|
|
598
|
-
break;
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
const attachmentObj = typeof attachment.toJSON === "function" ? attachment.toJSON() : { ...attachment };
|
|
603
|
-
if (matchedKey !== null) {
|
|
604
|
-
attachmentObj.storageId = matchedKey;
|
|
605
|
-
}
|
|
723
|
+
const attachmentObj = await this.prepareAttachmentForFileManager(attachment, fileManager);
|
|
606
724
|
const { stream } = await fileManager.getFileStream(attachmentObj);
|
|
607
725
|
const chunks = [];
|
|
608
726
|
for await (const chunk of stream) {
|
|
@@ -610,6 +728,225 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
|
|
|
610
728
|
}
|
|
611
729
|
return Buffer.concat(chunks).toString("utf-8");
|
|
612
730
|
}
|
|
731
|
+
async prepareAttachmentForFileManager(attachment, fileManager, recordCollection) {
|
|
732
|
+
const attachmentObj = typeof attachment.toJSON === "function" ? attachment.toJSON() : { ...attachment };
|
|
733
|
+
const collection = this.resolveCollection(
|
|
734
|
+
recordCollection || attachmentObj.collectionName || attachment.collectionName
|
|
735
|
+
);
|
|
736
|
+
const storageId = await this.resolveStorageId(attachment, attachmentObj, fileManager, collection);
|
|
737
|
+
if (!isMissingFileValue(storageId)) {
|
|
738
|
+
attachmentObj.storageId = storageId;
|
|
739
|
+
}
|
|
740
|
+
this.copyFileFieldsFromRecord(attachment, attachmentObj);
|
|
741
|
+
await this.ensureFileFields(attachment, attachmentObj, collection);
|
|
742
|
+
this.copyFileFieldsFromRecord(attachment, attachmentObj);
|
|
743
|
+
if (attachmentObj.storageId && attachmentObj.url) {
|
|
744
|
+
const storageModel = getStorageFromCache(fileManager.storagesCache, attachmentObj.storageId);
|
|
745
|
+
if (storageModel) {
|
|
746
|
+
const baseUrl = storageModel.baseUrl || "";
|
|
747
|
+
let relativeUrl = attachmentObj.url.split("?")[0];
|
|
748
|
+
if (baseUrl && relativeUrl.includes(baseUrl)) {
|
|
749
|
+
relativeUrl = relativeUrl.substring(relativeUrl.indexOf(baseUrl) + baseUrl.length);
|
|
750
|
+
}
|
|
751
|
+
relativeUrl = relativeUrl.replace(/^\/|\/$/g, "");
|
|
752
|
+
if (relativeUrl) {
|
|
753
|
+
const parts = relativeUrl.split("/");
|
|
754
|
+
const filename = parts.pop();
|
|
755
|
+
const filePath = parts.join("/");
|
|
756
|
+
if (filename && (!attachmentObj.filename || attachmentObj.filename === "file")) {
|
|
757
|
+
attachmentObj.filename = filename;
|
|
758
|
+
}
|
|
759
|
+
if (filePath && !attachmentObj.path) {
|
|
760
|
+
attachmentObj.path = filePath;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return attachmentObj;
|
|
766
|
+
}
|
|
767
|
+
resolveCollection(recordCollection) {
|
|
768
|
+
if (!recordCollection) {
|
|
769
|
+
return void 0;
|
|
770
|
+
}
|
|
771
|
+
if (typeof recordCollection === "string") {
|
|
772
|
+
return this.db.getCollection(recordCollection);
|
|
773
|
+
}
|
|
774
|
+
return recordCollection;
|
|
775
|
+
}
|
|
776
|
+
async resolveStorageId(record, recordObj, fileManager, recordCollection) {
|
|
777
|
+
var _a, _b, _c, _d, _e;
|
|
778
|
+
const collectionName = (recordCollection == null ? void 0 : recordCollection.name) || (recordObj == null ? void 0 : recordObj.collectionName) || (record == null ? void 0 : record.collectionName);
|
|
779
|
+
const rawStorageId = getRecordStorageId(record) ?? (recordObj == null ? void 0 : recordObj.storageId) ?? (recordObj == null ? void 0 : recordObj.storage_id) ?? ((_a = recordObj == null ? void 0 : recordObj.storage) == null ? void 0 : _a.id) ?? ((_b = recordObj == null ? void 0 : recordObj.storage) == null ? void 0 : _b.filterByTk);
|
|
780
|
+
const matchedCacheKey = findStorageCacheKey(fileManager == null ? void 0 : fileManager.storagesCache, rawStorageId);
|
|
781
|
+
if (!isMissingFileValue(matchedCacheKey)) {
|
|
782
|
+
this.log.debug(
|
|
783
|
+
`[FilePreviewAuth] storageId resolved from cache ${safeDebugJson({
|
|
784
|
+
collection: collectionName,
|
|
785
|
+
record: summarizeAttachmentForLog(recordObj, collectionName),
|
|
786
|
+
rawStorageId: toDebugValue(rawStorageId),
|
|
787
|
+
matchedCacheKey: toDebugValue(matchedCacheKey)
|
|
788
|
+
})}`
|
|
789
|
+
);
|
|
790
|
+
return matchedCacheKey;
|
|
791
|
+
}
|
|
792
|
+
const storageName = ((_c = recordObj == null ? void 0 : recordObj.storage) == null ? void 0 : _c.name) || (recordObj == null ? void 0 : recordObj.storageName);
|
|
793
|
+
if (storageName && (fileManager == null ? void 0 : fileManager.storagesCache)) {
|
|
794
|
+
for (const [key, storage2] of fileManager.storagesCache.entries()) {
|
|
795
|
+
if ((storage2 == null ? void 0 : storage2.name) === storageName) {
|
|
796
|
+
this.log.debug(
|
|
797
|
+
`[FilePreviewAuth] storageId resolved by storage name ${safeDebugJson({
|
|
798
|
+
collection: collectionName,
|
|
799
|
+
storageName,
|
|
800
|
+
matchedCacheKey: toDebugValue(key),
|
|
801
|
+
storage: summarizeStorageForLog(storage2)
|
|
802
|
+
})}`
|
|
803
|
+
);
|
|
804
|
+
return key;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
const dbStorageId = await this.resolveStorageIdFromRecordTable(record, recordCollection);
|
|
809
|
+
const matchedDbCacheKey = findStorageCacheKey(fileManager == null ? void 0 : fileManager.storagesCache, dbStorageId);
|
|
810
|
+
if (!isMissingFileValue(matchedDbCacheKey)) {
|
|
811
|
+
this.log.debug(
|
|
812
|
+
`[FilePreviewAuth] storageId resolved from DB column and cache ${safeDebugJson({
|
|
813
|
+
collection: collectionName,
|
|
814
|
+
dbStorageId: toDebugValue(dbStorageId),
|
|
815
|
+
matchedCacheKey: toDebugValue(matchedDbCacheKey)
|
|
816
|
+
})}`
|
|
817
|
+
);
|
|
818
|
+
return matchedDbCacheKey;
|
|
819
|
+
}
|
|
820
|
+
const storageId = !isMissingFileValue(dbStorageId) ? dbStorageId : rawStorageId;
|
|
821
|
+
if (isMissingFileValue(storageId)) {
|
|
822
|
+
const defaultStorageKey = findDefaultStorageCacheKey(fileManager == null ? void 0 : fileManager.storagesCache);
|
|
823
|
+
this.log.debug(
|
|
824
|
+
`[FilePreviewAuth] storageId missing; using default storage fallback ${safeDebugJson({
|
|
825
|
+
collection: collectionName,
|
|
826
|
+
defaultStorageKey: toDebugValue(defaultStorageKey),
|
|
827
|
+
storageCache: summarizeStorageCache(fileManager == null ? void 0 : fileManager.storagesCache)
|
|
828
|
+
})}`
|
|
829
|
+
);
|
|
830
|
+
return defaultStorageKey;
|
|
831
|
+
}
|
|
832
|
+
const storageRepo = this.db.getRepository("storages");
|
|
833
|
+
const storage = await storageRepo.findOne({ filterByTk: storageId });
|
|
834
|
+
if (!storage) {
|
|
835
|
+
this.log.debug(
|
|
836
|
+
`[FilePreviewAuth] storageId not found in storages table; returning raw value ${safeDebugJson({
|
|
837
|
+
collection: collectionName,
|
|
838
|
+
storageId: toDebugValue(storageId)
|
|
839
|
+
})}`
|
|
840
|
+
);
|
|
841
|
+
return storageId;
|
|
842
|
+
}
|
|
843
|
+
const parsedStorage = typeof (fileManager == null ? void 0 : fileManager.parseStorage) === "function" ? fileManager.parseStorage(storage) : storage.toJSON();
|
|
844
|
+
(_e = (_d = fileManager == null ? void 0 : fileManager.storagesCache) == null ? void 0 : _d.set) == null ? void 0 : _e.call(_d, storage.get("id"), parsedStorage);
|
|
845
|
+
this.log.debug(
|
|
846
|
+
`[FilePreviewAuth] storageId resolved from storages table ${safeDebugJson({
|
|
847
|
+
collection: collectionName,
|
|
848
|
+
requestedStorageId: toDebugValue(storageId),
|
|
849
|
+
resolvedStorageId: toDebugValue(storage.get("id")),
|
|
850
|
+
storage: summarizeStorageForLog(parsedStorage)
|
|
851
|
+
})}`
|
|
852
|
+
);
|
|
853
|
+
return storage.get("id");
|
|
854
|
+
}
|
|
855
|
+
async resolveStorageIdFromRecordTable(record, recordCollection) {
|
|
856
|
+
var _a, _b, _c, _d, _e;
|
|
857
|
+
const collection = recordCollection || ((_a = record == null ? void 0 : record.constructor) == null ? void 0 : _a.collection);
|
|
858
|
+
if (!(collection == null ? void 0 : collection.model)) {
|
|
859
|
+
return void 0;
|
|
860
|
+
}
|
|
861
|
+
const primaryKey = collection.model.primaryKeyAttribute || "id";
|
|
862
|
+
const recordId = ((_b = record.get) == null ? void 0 : _b.call(record, primaryKey)) ?? ((_c = record.get) == null ? void 0 : _c.call(record, "id")) ?? record[primaryKey] ?? record.id;
|
|
863
|
+
if (isMissingFileValue(recordId)) {
|
|
864
|
+
return void 0;
|
|
865
|
+
}
|
|
866
|
+
let columns;
|
|
867
|
+
try {
|
|
868
|
+
columns = await this.db.sequelize.getQueryInterface().describeTable(collection.getTableNameWithSchema());
|
|
869
|
+
} catch (error) {
|
|
870
|
+
this.log.warn(`[FilePreviewAuth] Failed to inspect table "${collection.name}" for storageId:`, error.message);
|
|
871
|
+
return void 0;
|
|
872
|
+
}
|
|
873
|
+
const rawAttributes = collection.model.rawAttributes || {};
|
|
874
|
+
const storageColumn = findExistingColumn(columns, [
|
|
875
|
+
(_d = rawAttributes.storageId) == null ? void 0 : _d.field,
|
|
876
|
+
(_e = rawAttributes.storage_id) == null ? void 0 : _e.field,
|
|
877
|
+
"storageId",
|
|
878
|
+
"storage_id",
|
|
879
|
+
"storageid"
|
|
880
|
+
]);
|
|
881
|
+
if (!storageColumn) {
|
|
882
|
+
return void 0;
|
|
883
|
+
}
|
|
884
|
+
const result = await collection.model.findOne({
|
|
885
|
+
attributes: [[(0, import_sequelize.col)(storageColumn), "storageId"]],
|
|
886
|
+
where: { [primaryKey]: recordId },
|
|
887
|
+
raw: true
|
|
888
|
+
});
|
|
889
|
+
this.log.debug(
|
|
890
|
+
`[FilePreviewAuth] storageId DB lookup ${safeDebugJson({
|
|
891
|
+
collection: collection.name,
|
|
892
|
+
table: String(collection.getTableNameWithSchema()),
|
|
893
|
+
primaryKey,
|
|
894
|
+
recordId: toDebugValue(recordId),
|
|
895
|
+
storageColumn,
|
|
896
|
+
dbStorageId: toDebugValue(result == null ? void 0 : result["storageId"])
|
|
897
|
+
})}`
|
|
898
|
+
);
|
|
899
|
+
return result == null ? void 0 : result["storageId"];
|
|
900
|
+
}
|
|
901
|
+
async ensureFileFields(record, recordObj, recordCollection) {
|
|
902
|
+
const fileFields = ["key", "filename", "path", "mimetype", "title", "extname", "url"];
|
|
903
|
+
const needsFileKey = !hasText(recordObj.key) && !hasText(recordObj.filename) && !hasText(recordObj.url);
|
|
904
|
+
const missingFields = fileFields.filter((field) => isMissingFileValue(recordObj[field]));
|
|
905
|
+
if (!needsFileKey && missingFields.length === 0) {
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
const fileData = await this.resolveFileFieldsFromRecordTable(record, fileFields, recordCollection);
|
|
909
|
+
for (const field of fileFields) {
|
|
910
|
+
if (!isMissingFileValue(fileData[field])) {
|
|
911
|
+
recordObj[field] = fileData[field];
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
copyFileFieldsFromRecord(record, recordObj) {
|
|
916
|
+
var _a, _b;
|
|
917
|
+
for (const field of ["key", "filename", "path", "mimetype", "title", "extname", "url"]) {
|
|
918
|
+
if (!isMissingFileValue(recordObj[field])) {
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
const value = ((_a = record.get) == null ? void 0 : _a.call(record, field)) ?? ((_b = record.getDataValue) == null ? void 0 : _b.call(record, field)) ?? record[field];
|
|
922
|
+
if (!isMissingFileValue(value)) {
|
|
923
|
+
recordObj[field] = value;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
async resolveFileFieldsFromRecordTable(record, fields, recordCollection) {
|
|
928
|
+
var _a, _b, _c;
|
|
929
|
+
const collection = recordCollection || ((_a = record == null ? void 0 : record.constructor) == null ? void 0 : _a.collection);
|
|
930
|
+
if (!(collection == null ? void 0 : collection.model)) {
|
|
931
|
+
return {};
|
|
932
|
+
}
|
|
933
|
+
const primaryKey = collection.model.primaryKeyAttribute || "id";
|
|
934
|
+
const recordId = ((_b = record.get) == null ? void 0 : _b.call(record, primaryKey)) ?? ((_c = record.get) == null ? void 0 : _c.call(record, "id")) ?? record[primaryKey] ?? record.id;
|
|
935
|
+
if (isMissingFileValue(recordId)) {
|
|
936
|
+
return {};
|
|
937
|
+
}
|
|
938
|
+
const rawAttributes = collection.model.rawAttributes || {};
|
|
939
|
+
const attributes = fields.filter((field) => rawAttributes[field]).map((field) => [(0, import_sequelize.col)(rawAttributes[field].field || field), field]);
|
|
940
|
+
if (attributes.length === 0) {
|
|
941
|
+
return {};
|
|
942
|
+
}
|
|
943
|
+
const result = await collection.model.findOne({
|
|
944
|
+
attributes,
|
|
945
|
+
where: { [primaryKey]: recordId },
|
|
946
|
+
raw: true
|
|
947
|
+
});
|
|
948
|
+
return result || {};
|
|
949
|
+
}
|
|
613
950
|
formatAttachmentWorkContext(attachment, text) {
|
|
614
951
|
const filename = sanitizeXmlAttr(getAttachmentDisplayName(attachment));
|
|
615
952
|
const mimetype = sanitizeXmlAttr(getAttachmentValue(attachment, "mimetype") || "");
|
|
@@ -649,6 +986,84 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
|
|
|
649
986
|
}
|
|
650
987
|
}
|
|
651
988
|
var plugin_default = PluginFilePreviewAuthServer;
|
|
989
|
+
function safeDebugJson(value) {
|
|
990
|
+
try {
|
|
991
|
+
return JSON.stringify(value, (_key, item) => typeof item === "bigint" ? item.toString() : item);
|
|
992
|
+
} catch (error) {
|
|
993
|
+
return JSON.stringify({ error: "failed_to_serialize_debug_payload" });
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
function toDebugValue(value) {
|
|
997
|
+
if (value === void 0 || value === null || value === "") {
|
|
998
|
+
return value;
|
|
999
|
+
}
|
|
1000
|
+
return String(value);
|
|
1001
|
+
}
|
|
1002
|
+
function getObjectKeys(value) {
|
|
1003
|
+
if (!value || typeof value !== "object") {
|
|
1004
|
+
return [];
|
|
1005
|
+
}
|
|
1006
|
+
return Object.keys(value);
|
|
1007
|
+
}
|
|
1008
|
+
function getDebugUrlPath(url) {
|
|
1009
|
+
if (!url) {
|
|
1010
|
+
return "";
|
|
1011
|
+
}
|
|
1012
|
+
try {
|
|
1013
|
+
return new URL(url, "http://local").pathname;
|
|
1014
|
+
} catch {
|
|
1015
|
+
return "unparseable";
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
function summarizeAttachmentForLog(attachment, collectionName) {
|
|
1019
|
+
var _a;
|
|
1020
|
+
return {
|
|
1021
|
+
id: toDebugValue(getAttachmentValue(attachment, "id")),
|
|
1022
|
+
uid: toDebugValue(getAttachmentValue(attachment, "uid")),
|
|
1023
|
+
collectionName: String(collectionName || getAttachmentValue(attachment, "collectionName") || ""),
|
|
1024
|
+
storageId: toDebugValue(getAttachmentValue(attachment, "storageId")),
|
|
1025
|
+
storageIdColumn: toDebugValue(getAttachmentValue(attachment, "storage_id")),
|
|
1026
|
+
storageName: ((_a = getAttachmentValue(attachment, "storage")) == null ? void 0 : _a.name) || getAttachmentValue(attachment, "storageName"),
|
|
1027
|
+
fieldsPresent: {
|
|
1028
|
+
key: hasText(getAttachmentValue(attachment, "key")),
|
|
1029
|
+
filename: hasText(getAttachmentValue(attachment, "filename")),
|
|
1030
|
+
path: hasText(getAttachmentValue(attachment, "path")),
|
|
1031
|
+
url: hasText(getAttachmentValue(attachment, "url")),
|
|
1032
|
+
preview: hasText(getAttachmentValue(attachment, "preview")),
|
|
1033
|
+
mimetype: hasText(getAttachmentValue(attachment, "mimetype")),
|
|
1034
|
+
title: hasText(getAttachmentValue(attachment, "title")),
|
|
1035
|
+
extname: hasText(getAttachmentValue(attachment, "extname"))
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
function summarizeStorageForLog(storage) {
|
|
1040
|
+
var _a, _b, _c;
|
|
1041
|
+
if (!storage) {
|
|
1042
|
+
return null;
|
|
1043
|
+
}
|
|
1044
|
+
return {
|
|
1045
|
+
id: toDebugValue(storage.id),
|
|
1046
|
+
name: storage.name,
|
|
1047
|
+
type: storage.type,
|
|
1048
|
+
default: Boolean(storage.default),
|
|
1049
|
+
public: Boolean((_a = storage.options) == null ? void 0 : _a.public),
|
|
1050
|
+
paranoid: Boolean(storage.paranoid),
|
|
1051
|
+
hasBucket: hasText((_b = storage.options) == null ? void 0 : _b.bucket),
|
|
1052
|
+
hasEndpoint: hasText((_c = storage.options) == null ? void 0 : _c.endpoint)
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
function summarizeStorageCache(cache) {
|
|
1056
|
+
if (!cache) {
|
|
1057
|
+
return { size: 0, storages: [] };
|
|
1058
|
+
}
|
|
1059
|
+
return {
|
|
1060
|
+
size: cache.size,
|
|
1061
|
+
storages: Array.from(cache.entries()).slice(0, 20).map(([key, storage]) => ({
|
|
1062
|
+
cacheKey: toDebugValue(key),
|
|
1063
|
+
...summarizeStorageForLog(storage)
|
|
1064
|
+
}))
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
652
1067
|
function getStorageFromCache(cache, storageId) {
|
|
653
1068
|
if (storageId === void 0 || storageId === null) return void 0;
|
|
654
1069
|
let res = cache.get(storageId);
|
|
@@ -673,6 +1088,67 @@ function getAttachmentValue(attachment, key) {
|
|
|
673
1088
|
if (typeof attachment.get === "function") return attachment.get(key);
|
|
674
1089
|
return attachment[key];
|
|
675
1090
|
}
|
|
1091
|
+
function getRecordStorageId(record) {
|
|
1092
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1093
|
+
if (!record) return void 0;
|
|
1094
|
+
return ((_a = record.get) == null ? void 0 : _a.call(record, "storageId")) ?? ((_b = record.get) == null ? void 0 : _b.call(record, "storage_id")) ?? ((_c = record.getDataValue) == null ? void 0 : _c.call(record, "storageId")) ?? ((_d = record.getDataValue) == null ? void 0 : _d.call(record, "storage_id")) ?? record.storageId ?? record.storage_id ?? ((_f = (_e = record.get) == null ? void 0 : _e.call(record, "storage")) == null ? void 0 : _f.id) ?? ((_g = record.storage) == null ? void 0 : _g.id);
|
|
1095
|
+
}
|
|
1096
|
+
function findStorageCacheKey(cache, storageId) {
|
|
1097
|
+
if (!cache || isMissingFileValue(storageId)) {
|
|
1098
|
+
return void 0;
|
|
1099
|
+
}
|
|
1100
|
+
if (cache.has(storageId)) {
|
|
1101
|
+
return storageId;
|
|
1102
|
+
}
|
|
1103
|
+
const strId = String(storageId);
|
|
1104
|
+
if (cache.has(strId)) {
|
|
1105
|
+
return strId;
|
|
1106
|
+
}
|
|
1107
|
+
const numericId = Number(storageId);
|
|
1108
|
+
if (Number.isFinite(numericId) && cache.has(numericId)) {
|
|
1109
|
+
return numericId;
|
|
1110
|
+
}
|
|
1111
|
+
for (const key of cache.keys()) {
|
|
1112
|
+
if (String(key) === strId) {
|
|
1113
|
+
return key;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
return void 0;
|
|
1117
|
+
}
|
|
1118
|
+
function findDefaultStorageCacheKey(cache) {
|
|
1119
|
+
if (!cache) {
|
|
1120
|
+
return void 0;
|
|
1121
|
+
}
|
|
1122
|
+
for (const [key, storage] of cache.entries()) {
|
|
1123
|
+
if (storage == null ? void 0 : storage.default) {
|
|
1124
|
+
return key;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
if (cache.size === 1) {
|
|
1128
|
+
return cache.keys().next().value;
|
|
1129
|
+
}
|
|
1130
|
+
return void 0;
|
|
1131
|
+
}
|
|
1132
|
+
function findExistingColumn(columns, candidates) {
|
|
1133
|
+
const columnNames = Object.keys(columns || {});
|
|
1134
|
+
for (const candidate of candidates) {
|
|
1135
|
+
if (!candidate) continue;
|
|
1136
|
+
if (Object.prototype.hasOwnProperty.call(columns, candidate)) {
|
|
1137
|
+
return candidate;
|
|
1138
|
+
}
|
|
1139
|
+
const matchedColumn = columnNames.find((columnName) => columnName.toLowerCase() === candidate.toLowerCase());
|
|
1140
|
+
if (matchedColumn) {
|
|
1141
|
+
return matchedColumn;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
return void 0;
|
|
1145
|
+
}
|
|
1146
|
+
function hasText(value) {
|
|
1147
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
1148
|
+
}
|
|
1149
|
+
function isMissingFileValue(value) {
|
|
1150
|
+
return value === void 0 || value === null || value === "";
|
|
1151
|
+
}
|
|
676
1152
|
function getAttachmentDisplayName(attachment) {
|
|
677
1153
|
const title = getAttachmentValue(attachment, "title");
|
|
678
1154
|
const extname = getAttachmentValue(attachment, "extname");
|