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.
@@ -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
- let matchedKey = null;
249
- const rawStorageId = attachment.storageId || getAttachmentValue(attachment, "storageId");
250
- if (rawStorageId) {
251
- const strId = String(rawStorageId);
252
- for (const key of fileManager.storagesCache.keys()) {
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 repo.update({
309
- filterByTk: attachmentId,
310
- values: { ocrStatus: "pending-ocr" }
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 = { ok: true, ocrStatus: "pending-ocr" };
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((role) => role === "root" || role === "admin" || (role == null ? void 0 : role.name) === "root" || (role == null ? void 0 : role.name) === "admin");
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("[FilePreviewAuth] plugin-markitdown-parser not found; uploaded raw text parsing fallback will be used");
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
- let matchedKey = null;
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");