@open-mercato/core 0.6.2-develop.3381.1.8461b312a9 → 0.6.2-develop.3400.2.d948268f50

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.
Files changed (64) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/attachments/api/file/[id]/route.js +8 -8
  3. package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
  4. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +7 -8
  5. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
  6. package/dist/modules/attachments/api/library/[id]/route.js +6 -2
  7. package/dist/modules/attachments/api/library/[id]/route.js.map +2 -2
  8. package/dist/modules/attachments/api/library/route.js +2 -0
  9. package/dist/modules/attachments/api/library/route.js.map +2 -2
  10. package/dist/modules/attachments/api/openapi.js +5 -1
  11. package/dist/modules/attachments/api/openapi.js.map +2 -2
  12. package/dist/modules/attachments/api/partitions/route.js +10 -1
  13. package/dist/modules/attachments/api/partitions/route.js.map +2 -2
  14. package/dist/modules/attachments/api/route.js +19 -7
  15. package/dist/modules/attachments/api/route.js.map +2 -2
  16. package/dist/modules/attachments/backend/config/attachments/page.js +3 -1
  17. package/dist/modules/attachments/backend/config/attachments/page.js.map +2 -2
  18. package/dist/modules/attachments/cli.js +6 -2
  19. package/dist/modules/attachments/cli.js.map +2 -2
  20. package/dist/modules/attachments/components/AttachmentPartitionSettings.js +216 -10
  21. package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +2 -2
  22. package/dist/modules/attachments/di.js +13 -0
  23. package/dist/modules/attachments/di.js.map +7 -0
  24. package/dist/modules/attachments/lib/drivers/driverFactory.js +67 -0
  25. package/dist/modules/attachments/lib/drivers/driverFactory.js.map +7 -0
  26. package/dist/modules/attachments/lib/drivers/index.js +11 -0
  27. package/dist/modules/attachments/lib/drivers/index.js.map +7 -0
  28. package/dist/modules/attachments/lib/drivers/legacyPublicDriver.js +42 -0
  29. package/dist/modules/attachments/lib/drivers/legacyPublicDriver.js.map +7 -0
  30. package/dist/modules/attachments/lib/drivers/localDriver.js +70 -0
  31. package/dist/modules/attachments/lib/drivers/localDriver.js.map +7 -0
  32. package/dist/modules/attachments/lib/drivers/types.js +1 -0
  33. package/dist/modules/attachments/lib/drivers/types.js.map +7 -0
  34. package/dist/modules/attachments/lib/ocrQueue.js +10 -5
  35. package/dist/modules/attachments/lib/ocrQueue.js.map +2 -2
  36. package/dist/modules/attachments/lib/storage.js.map +2 -2
  37. package/dist/modules/attachments/lib/thumbnailCache.js +11 -13
  38. package/dist/modules/attachments/lib/thumbnailCache.js.map +2 -2
  39. package/dist/modules/integrations/backend/integrations/[id]/page.js +31 -5
  40. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  41. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +14 -2
  42. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
  43. package/package.json +11 -7
  44. package/src/modules/attachments/api/file/[id]/route.ts +10 -8
  45. package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +8 -8
  46. package/src/modules/attachments/api/library/[id]/route.ts +7 -3
  47. package/src/modules/attachments/api/library/route.ts +2 -0
  48. package/src/modules/attachments/api/openapi.ts +4 -0
  49. package/src/modules/attachments/api/partitions/route.ts +10 -1
  50. package/src/modules/attachments/api/route.ts +20 -7
  51. package/src/modules/attachments/backend/config/attachments/page.tsx +3 -1
  52. package/src/modules/attachments/cli.ts +7 -2
  53. package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +275 -7
  54. package/src/modules/attachments/di.ts +13 -0
  55. package/src/modules/attachments/lib/drivers/driverFactory.ts +91 -0
  56. package/src/modules/attachments/lib/drivers/index.ts +4 -0
  57. package/src/modules/attachments/lib/drivers/legacyPublicDriver.ts +46 -0
  58. package/src/modules/attachments/lib/drivers/localDriver.ts +79 -0
  59. package/src/modules/attachments/lib/drivers/types.ts +28 -0
  60. package/src/modules/attachments/lib/ocrQueue.ts +14 -6
  61. package/src/modules/attachments/lib/storage.ts +13 -0
  62. package/src/modules/attachments/lib/thumbnailCache.ts +25 -19
  63. package/src/modules/integrations/backend/integrations/[id]/page.tsx +30 -4
  64. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +15 -2
@@ -33,14 +33,29 @@ const DEFAULT_FORM = {
33
33
  description: "",
34
34
  isPublic: false,
35
35
  requiresOcr: true,
36
- ocrModel: ""
36
+ ocrModel: "",
37
+ storageDriver: "local",
38
+ s3CredentialsSource: "integration",
39
+ s3CredentialsEnvPrefix: "",
40
+ s3Bucket: "",
41
+ s3Region: "",
42
+ s3Endpoint: "",
43
+ s3ForcePathStyle: false
37
44
  };
45
+ const ALL_STORAGE_DRIVER_OPTIONS = [
46
+ { value: "local", label: "Local filesystem" },
47
+ { value: "s3", label: "Amazon S3 / S3-compatible", requiresModule: "s3" }
48
+ ];
49
+ const OCR_MODEL_DEFAULT_VALUE = "__default__";
38
50
  const OCR_MODEL_OPTIONS = [
39
- { value: "", label: "Default (from environment)" },
51
+ { value: OCR_MODEL_DEFAULT_VALUE, label: "Default (from environment)" },
40
52
  { value: "gpt-4o", label: "GPT-4o (Recommended)" },
41
53
  { value: "gpt-4o-mini", label: "GPT-4o Mini (Faster, Lower Cost)" }
42
54
  ];
43
- function AttachmentPartitionSettings() {
55
+ function AttachmentPartitionSettings({ s3Enabled }) {
56
+ const storageDriverOptions = ALL_STORAGE_DRIVER_OPTIONS.filter(
57
+ (opt) => !opt.requiresModule || opt.requiresModule === "s3" && s3Enabled
58
+ );
44
59
  const t = useT();
45
60
  const { confirm, ConfirmDialogElement } = useConfirmDialog();
46
61
  const [items, setItems] = React.useState([]);
@@ -50,6 +65,8 @@ function AttachmentPartitionSettings() {
50
65
  const [form, setForm] = React.useState(DEFAULT_FORM);
51
66
  const [submitting, setSubmitting] = React.useState(false);
52
67
  const [error, setError] = React.useState(null);
68
+ const s3Unavailable = !s3Enabled;
69
+ const s3SelectedWhileUnavailable = s3Unavailable && form.storageDriver === "s3";
53
70
  const loadErrorMessage = t("attachments.partitions.errors.load", "Failed to load partitions.");
54
71
  const loadItems = React.useCallback(async () => {
55
72
  setLoading(true);
@@ -85,13 +102,22 @@ function AttachmentPartitionSettings() {
85
102
  }, [items, search]);
86
103
  const openDialog = React.useCallback((state) => {
87
104
  if (state.mode === "edit") {
105
+ const cfg = state.entry.configJson ?? {};
106
+ const credsPrefix = typeof cfg.credentialsEnvPrefix === "string" ? cfg.credentialsEnvPrefix : "";
88
107
  setForm({
89
108
  code: state.entry.code,
90
109
  title: state.entry.title,
91
110
  description: state.entry.description ?? "",
92
111
  isPublic: state.entry.isPublic,
93
112
  requiresOcr: state.entry.requiresOcr,
94
- ocrModel: state.entry.ocrModel ?? ""
113
+ ocrModel: state.entry.ocrModel ?? "",
114
+ storageDriver: state.entry.storageDriver ?? "local",
115
+ s3CredentialsSource: credsPrefix ? "env" : "integration",
116
+ s3CredentialsEnvPrefix: credsPrefix,
117
+ s3Bucket: typeof cfg.bucket === "string" ? cfg.bucket : "",
118
+ s3Region: typeof cfg.region === "string" ? cfg.region : "",
119
+ s3Endpoint: typeof cfg.endpoint === "string" ? cfg.endpoint : "",
120
+ s3ForcePathStyle: cfg.forcePathStyle === true
95
121
  });
96
122
  } else {
97
123
  setForm(DEFAULT_FORM);
@@ -113,16 +139,45 @@ function AttachmentPartitionSettings() {
113
139
  setError(t("attachments.partitions.errors.required", "Code and title are required."));
114
140
  return;
115
141
  }
142
+ if (form.storageDriver === "s3" && form.s3CredentialsSource === "env") {
143
+ if (!form.s3CredentialsEnvPrefix.trim()) {
144
+ setError(
145
+ t(
146
+ "attachments.partitions.errors.s3EnvPrefixRequired",
147
+ "Credentials env prefix is required when using a separate credentials source."
148
+ )
149
+ );
150
+ return;
151
+ }
152
+ if (!form.s3Bucket.trim()) {
153
+ setError(
154
+ t(
155
+ "attachments.partitions.errors.s3BucketRequired",
156
+ "Bucket is required when using a separate credentials source."
157
+ )
158
+ );
159
+ return;
160
+ }
161
+ }
116
162
  setSubmitting(true);
117
163
  setError(null);
118
164
  try {
165
+ const s3ConfigJson = form.storageDriver === "s3" ? {
166
+ bucket: form.s3Bucket.trim() || void 0,
167
+ region: form.s3Region.trim() || void 0,
168
+ endpoint: form.s3Endpoint.trim() || void 0,
169
+ forcePathStyle: form.s3ForcePathStyle || void 0,
170
+ ...form.s3CredentialsSource === "env" ? { credentialsEnvPrefix: form.s3CredentialsEnvPrefix.trim() || void 0 } : {}
171
+ } : null;
119
172
  const payload = {
120
173
  code: trimmedCode,
121
174
  title: trimmedTitle,
122
175
  description: form.description.trim() || void 0,
123
176
  isPublic: form.isPublic,
124
177
  requiresOcr: form.requiresOcr,
125
- ocrModel: form.ocrModel.trim() || null
178
+ ocrModel: form.ocrModel.trim() || null,
179
+ storageDriver: form.storageDriver,
180
+ configJson: s3ConfigJson
126
181
  };
127
182
  const method = dialog.mode === "create" ? "POST" : "PUT";
128
183
  const body = dialog.mode === "edit" ? JSON.stringify({ id: dialog.entry.id, ...payload }) : JSON.stringify(payload);
@@ -206,6 +261,11 @@ function AttachmentPartitionSettings() {
206
261
  accessorKey: "requiresOcr",
207
262
  cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-sm", children: row.original.requiresOcr ? t("common.enabled", "Enabled") : t("common.disabled", "Disabled") })
208
263
  },
264
+ {
265
+ header: t("attachments.partitions.table.storageDriver", "Storage"),
266
+ accessorKey: "storageDriver",
267
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-sm capitalize", children: row.original.storageDriver === "s3" ? "S3" : row.original.storageDriver ?? "local" })
268
+ },
209
269
  {
210
270
  header: t("attachments.partitions.table.envKey", "Env variable"),
211
271
  accessorKey: "envKey",
@@ -362,11 +422,17 @@ function AttachmentPartitionSettings() {
362
422
  /* @__PURE__ */ jsxs(
363
423
  Select,
364
424
  {
365
- value: form.ocrModel || void 0,
366
- onValueChange: (value) => setForm((prev) => ({ ...prev, ocrModel: value ?? "" })),
425
+ value: form.ocrModel ? form.ocrModel : OCR_MODEL_DEFAULT_VALUE,
426
+ onValueChange: (value) => setForm((prev) => ({
427
+ ...prev,
428
+ ocrModel: value === OCR_MODEL_DEFAULT_VALUE ? "" : value
429
+ })),
367
430
  children: [
368
431
  /* @__PURE__ */ jsx(SelectTrigger, { id: "partition-ocr-model", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
369
- /* @__PURE__ */ jsx(SelectContent, { children: OCR_MODEL_OPTIONS.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option.value, children: t(`attachments.partitions.form.ocrModelOptions.${option.value || "default"}`, option.label) }, option.value)) })
432
+ /* @__PURE__ */ jsx(SelectContent, { children: OCR_MODEL_OPTIONS.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option.value, children: t(
433
+ `attachments.partitions.form.ocrModelOptions.${option.value === OCR_MODEL_DEFAULT_VALUE ? "default" : option.value}`,
434
+ option.label
435
+ ) }, option.value)) })
370
436
  ]
371
437
  }
372
438
  ),
@@ -375,17 +441,157 @@ function AttachmentPartitionSettings() {
375
441
  "Choose the LLM model for OCR processing. Falls back to OCR_MODEL environment variable or gpt-4o."
376
442
  ) })
377
443
  ] }),
444
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
445
+ /* @__PURE__ */ jsx(Label, { htmlFor: "partition-storage-driver", children: t("attachments.partitions.form.storageDriverLabel", "Storage Driver") }),
446
+ /* @__PURE__ */ jsx(
447
+ "select",
448
+ {
449
+ id: "partition-storage-driver",
450
+ className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
451
+ value: form.storageDriver,
452
+ onChange: (event) => setForm((prev) => ({ ...prev, storageDriver: event.target.value })),
453
+ children: storageDriverOptions.map((option) => /* @__PURE__ */ jsx(
454
+ "option",
455
+ {
456
+ value: option.value,
457
+ disabled: option.value === "s3" && s3Unavailable,
458
+ children: option.label
459
+ },
460
+ option.value
461
+ ))
462
+ }
463
+ ),
464
+ s3Unavailable ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t(
465
+ "attachments.partitions.form.storageDriverS3DisabledHelp",
466
+ "S3 is disabled. Set OM_ENABLE_STORAGE_S3=true and restart to enable S3 partitions."
467
+ ) }) : null
468
+ ] }),
469
+ form.storageDriver === "s3" && /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-md border bg-muted/30 p-3", children: [
470
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: t("attachments.partitions.form.s3ConfigTitle", "S3 Configuration") }),
471
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
472
+ /* @__PURE__ */ jsx(Label, { htmlFor: "partition-s3-creds-source", children: t("attachments.partitions.form.s3CredsSourceLabel", "Credentials source") }),
473
+ /* @__PURE__ */ jsxs(
474
+ "select",
475
+ {
476
+ id: "partition-s3-creds-source",
477
+ className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
478
+ value: form.s3CredentialsSource,
479
+ onChange: (event) => setForm((prev) => ({
480
+ ...prev,
481
+ s3CredentialsSource: event.target.value,
482
+ ...event.target.value === "integration" ? { s3CredentialsEnvPrefix: "" } : {}
483
+ })),
484
+ children: [
485
+ /* @__PURE__ */ jsx("option", { value: "integration", children: t(
486
+ "attachments.partitions.form.s3CredsSourceIntegration",
487
+ "Use Integration Marketplace credentials"
488
+ ) }),
489
+ /* @__PURE__ */ jsx("option", { value: "env", children: t(
490
+ "attachments.partitions.form.s3CredsSourceEnv",
491
+ "Use separate env-based credentials"
492
+ ) })
493
+ ]
494
+ }
495
+ ),
496
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: form.s3CredentialsSource === "integration" ? t(
497
+ "attachments.partitions.form.s3CredsSourceIntegrationHelp",
498
+ "This partition reuses the credentials, bucket, region and endpoint configured in the S3 integration. Override individual fields below only when needed."
499
+ ) : t(
500
+ "attachments.partitions.form.s3CredsSourceEnvHelp",
501
+ "This partition uses its own credentials read from environment variables. The integration credentials are ignored. Bucket and credentials prefix are required."
502
+ ) })
503
+ ] }),
504
+ form.s3CredentialsSource === "env" && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
505
+ /* @__PURE__ */ jsx(Label, { htmlFor: "partition-s3-creds-prefix", children: t("attachments.partitions.form.s3CredsPrefixLabel", "Credentials Env Prefix") }),
506
+ /* @__PURE__ */ jsx(
507
+ Input,
508
+ {
509
+ id: "partition-s3-creds-prefix",
510
+ value: form.s3CredentialsEnvPrefix,
511
+ onChange: (event) => setForm((prev) => ({ ...prev, s3CredentialsEnvPrefix: event.target.value })),
512
+ placeholder: "OM_INTEGRATION_STORAGE_S3",
513
+ className: "font-mono"
514
+ }
515
+ ),
516
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t(
517
+ "attachments.partitions.form.s3CredsPrefixHelp",
518
+ "Reads {PREFIX}_ACCESS_KEY_ID and {PREFIX}_SECRET_ACCESS_KEY from environment variables."
519
+ ) })
520
+ ] }),
521
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
522
+ /* @__PURE__ */ jsx(Label, { htmlFor: "partition-s3-bucket", children: form.s3CredentialsSource === "integration" ? t("attachments.partitions.form.s3BucketOverrideLabel", "Bucket override (optional)") : t("attachments.partitions.form.s3BucketLabel", "Bucket") }),
523
+ /* @__PURE__ */ jsx(
524
+ Input,
525
+ {
526
+ id: "partition-s3-bucket",
527
+ value: form.s3Bucket,
528
+ onChange: (event) => setForm((prev) => ({ ...prev, s3Bucket: event.target.value })),
529
+ placeholder: form.s3CredentialsSource === "integration" ? t(
530
+ "attachments.partitions.form.s3BucketOverridePlaceholder",
531
+ "Leave empty to use integration bucket"
532
+ ) : "my-company-attachments"
533
+ }
534
+ )
535
+ ] }),
536
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
537
+ /* @__PURE__ */ jsx(Label, { htmlFor: "partition-s3-region", children: form.s3CredentialsSource === "integration" ? t("attachments.partitions.form.s3RegionOverrideLabel", "Region override (optional)") : t("attachments.partitions.form.s3RegionLabel", "Region") }),
538
+ /* @__PURE__ */ jsx(
539
+ Input,
540
+ {
541
+ id: "partition-s3-region",
542
+ value: form.s3Region,
543
+ onChange: (event) => setForm((prev) => ({ ...prev, s3Region: event.target.value })),
544
+ placeholder: form.s3CredentialsSource === "integration" ? t(
545
+ "attachments.partitions.form.s3RegionOverridePlaceholder",
546
+ "Leave empty to use integration region"
547
+ ) : "us-east-1"
548
+ }
549
+ )
550
+ ] }),
551
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
552
+ /* @__PURE__ */ jsx(Label, { htmlFor: "partition-s3-endpoint", children: form.s3CredentialsSource === "integration" ? t("attachments.partitions.form.s3EndpointOverrideLabel", "Custom endpoint override (optional)") : t("attachments.partitions.form.s3EndpointLabel", "Custom Endpoint") }),
553
+ /* @__PURE__ */ jsx(
554
+ Input,
555
+ {
556
+ id: "partition-s3-endpoint",
557
+ value: form.s3Endpoint,
558
+ onChange: (event) => setForm((prev) => ({ ...prev, s3Endpoint: event.target.value })),
559
+ placeholder: form.s3CredentialsSource === "integration" ? t(
560
+ "attachments.partitions.form.s3EndpointOverridePlaceholder",
561
+ "Leave empty to use integration endpoint"
562
+ ) : "https://fra1.digitaloceanspaces.com"
563
+ }
564
+ ),
565
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("attachments.partitions.form.s3EndpointHelp", "Leave empty for AWS S3. Required for MinIO, DigitalOcean Spaces, etc.") })
566
+ ] }),
567
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm font-medium", children: [
568
+ /* @__PURE__ */ jsx(
569
+ "input",
570
+ {
571
+ type: "checkbox",
572
+ className: "h-4 w-4 rounded border",
573
+ checked: form.s3ForcePathStyle,
574
+ onChange: (event) => setForm((prev) => ({ ...prev, s3ForcePathStyle: event.target.checked }))
575
+ }
576
+ ),
577
+ t("attachments.partitions.form.s3ForcePathStyleLabel", "Force Path Style (required for MinIO)")
578
+ ] })
579
+ ] }),
378
580
  dialog ? /* @__PURE__ */ jsxs("div", { className: "rounded-md border bg-muted/50 px-3 py-2 text-xs text-muted-foreground", children: [
379
581
  /* @__PURE__ */ jsx("div", { children: t("attachments.partitions.form.envKeyHelp", "Set this env var to override storage path:") }),
380
582
  /* @__PURE__ */ jsx("code", { children: dialog.mode === "edit" ? dialog.entry.envKey : form.code.trim() ? resolvePartitionEnvKey(form.code.trim()) : "ATTACHMENTS_PARTITION_CODE_ROOT" })
381
583
  ] }) : null,
382
- error ? /* @__PURE__ */ jsx("p", { className: "text-sm text-red-600", children: error }) : null
584
+ s3SelectedWhileUnavailable ? /* @__PURE__ */ jsx("p", { className: "text-sm text-status-error-text", children: t(
585
+ "attachments.partitions.errors.s3Disabled",
586
+ "This partition uses S3, but S3 is disabled. Set OM_ENABLE_STORAGE_S3=true and restart the app."
587
+ ) }) : null,
588
+ error ? /* @__PURE__ */ jsx("p", { className: "text-sm text-status-error-text", children: error }) : null
383
589
  ]
384
590
  }
385
591
  ),
386
592
  /* @__PURE__ */ jsxs(DialogFooter, { children: [
387
593
  /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: closeDialog, children: t("attachments.partitions.actions.cancel", "Cancel") }),
388
- /* @__PURE__ */ jsx(Button, { onClick: () => void handleSubmit(), disabled: submitting, children: dialog?.mode === "edit" ? t("attachments.partitions.actions.save", "Save changes") : t("attachments.partitions.actions.create", "Create partition") })
594
+ /* @__PURE__ */ jsx(Button, { onClick: () => void handleSubmit(), disabled: submitting || s3SelectedWhileUnavailable, children: dialog?.mode === "edit" ? t("attachments.partitions.actions.save", "Save changes") : t("attachments.partitions.actions.create", "Create partition") })
389
595
  ] })
390
596
  ] }) }),
391
597
  ConfirmDialogElement
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/attachments/components/AttachmentPartitionSettings.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { resolvePartitionEnvKey } from '@open-mercato/core/modules/attachments/lib/partitionEnv'\n\ntype Partition = {\n id: string\n code: string\n title: string\n description: string | null\n isPublic: boolean\n requiresOcr: boolean\n ocrModel: string | null\n envKey: string\n createdAt: string | null\n}\n\ntype DialogState =\n | { mode: 'create' }\n | { mode: 'edit'; entry: Partition }\n\nconst DEFAULT_FORM = {\n code: '',\n title: '',\n description: '',\n isPublic: false,\n requiresOcr: true,\n ocrModel: '',\n}\n\nconst OCR_MODEL_OPTIONS = [\n { value: '', label: 'Default (from environment)' },\n { value: 'gpt-4o', label: 'GPT-4o (Recommended)' },\n { value: 'gpt-4o-mini', label: 'GPT-4o Mini (Faster, Lower Cost)' },\n]\n\nexport function AttachmentPartitionSettings() {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [items, setItems] = React.useState<Partition[]>([])\n const [loading, setLoading] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [dialog, setDialog] = React.useState<DialogState | null>(null)\n const [form, setForm] = React.useState(DEFAULT_FORM)\n const [submitting, setSubmitting] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const loadErrorMessage = t('attachments.partitions.errors.load', 'Failed to load partitions.')\n\n const loadItems = React.useCallback(async () => {\n setLoading(true)\n try {\n const payload = await readApiResultOrThrow<{ items?: Partition[] }>(\n '/api/attachments/partitions',\n undefined,\n { errorMessage: loadErrorMessage },\n )\n const normalized = Array.isArray(payload.items) ? payload.items : []\n const withDefaults = normalized.map((entry) => ({\n ...entry,\n requiresOcr: typeof entry.requiresOcr === 'boolean' ? entry.requiresOcr : true,\n }))\n setItems(withDefaults)\n } catch (err) {\n console.error('[attachments.partitions] list failed', err)\n flash(loadErrorMessage, 'error')\n } finally {\n setLoading(false)\n }\n }, [loadErrorMessage])\n\n React.useEffect(() => {\n loadItems().catch(() => {})\n }, [loadItems])\n\n const filteredItems = React.useMemo(() => {\n if (!search.trim()) return items\n const term = search.trim().toLowerCase()\n return items.filter(\n (entry) =>\n entry.code.toLowerCase().includes(term) ||\n entry.title.toLowerCase().includes(term) ||\n (entry.description ?? '').toLowerCase().includes(term),\n )\n }, [items, search])\n\n const openDialog = React.useCallback((state: DialogState) => {\n if (state.mode === 'edit') {\n setForm({\n code: state.entry.code,\n title: state.entry.title,\n description: state.entry.description ?? '',\n isPublic: state.entry.isPublic,\n requiresOcr: state.entry.requiresOcr,\n ocrModel: state.entry.ocrModel ?? '',\n })\n } else {\n setForm(DEFAULT_FORM)\n }\n setError(null)\n setDialog(state)\n }, [])\n\n const closeDialog = React.useCallback(() => {\n setDialog(null)\n setError(null)\n setSubmitting(false)\n setForm(DEFAULT_FORM)\n }, [])\n\n const handleSubmit = React.useCallback(async () => {\n if (!dialog) return\n const trimmedCode = form.code.trim()\n const trimmedTitle = form.title.trim()\n if (!trimmedCode || !trimmedTitle) {\n setError(t('attachments.partitions.errors.required', 'Code and title are required.'))\n return\n }\n setSubmitting(true)\n setError(null)\n try {\n const payload = {\n code: trimmedCode,\n title: trimmedTitle,\n description: form.description.trim() || undefined,\n isPublic: form.isPublic,\n requiresOcr: form.requiresOcr,\n ocrModel: form.ocrModel.trim() || null,\n }\n const method = dialog.mode === 'create' ? 'POST' : 'PUT'\n const body =\n dialog.mode === 'edit'\n ? JSON.stringify({ id: dialog.entry.id, ...payload })\n : JSON.stringify(payload)\n const call = await apiCall('/api/attachments/partitions', {\n method,\n headers: { 'content-type': 'application/json' },\n body,\n })\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('attachments.partitions.errors.save', 'Failed to save partition.'),\n )\n }\n flash(\n dialog.mode === 'create'\n ? t('attachments.partitions.messages.created', 'Partition created.')\n : t('attachments.partitions.messages.updated', 'Partition updated.'),\n 'success',\n )\n closeDialog()\n await loadItems()\n } catch (err) {\n console.error('[attachments.partitions] save failed', err)\n const message =\n err instanceof Error ? err.message : t('attachments.partitions.errors.save', 'Failed to save partition.')\n setError(message)\n } finally {\n setSubmitting(false)\n }\n }, [dialog, form, t, closeDialog, loadItems])\n\n const handleDelete = React.useCallback(\n async (entry: Partition) => {\n const confirmMessage = t('attachments.partitions.confirm.delete', 'Delete partition \"{{code}}\"?').replace(\n '{{code}}',\n entry.code,\n )\n const confirmed = await confirm({\n title: confirmMessage,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const call = await apiCall(`/api/attachments/partitions?id=${encodeURIComponent(entry.id)}`, {\n method: 'DELETE',\n })\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('attachments.partitions.errors.delete', 'Failed to delete partition.'),\n )\n }\n flash(t('attachments.partitions.messages.deleted', 'Partition removed.'), 'success')\n await loadItems()\n } catch (err) {\n console.error('[attachments.partitions] delete failed', err)\n flash(t('attachments.partitions.errors.delete', 'Failed to delete partition.'), 'error')\n }\n },\n [confirm, loadItems, t],\n )\n\n const columns = React.useMemo<ColumnDef<Partition>[]>(\n () => [\n {\n header: t('attachments.partitions.table.code', 'Code'),\n accessorKey: 'code',\n cell: ({ row }) => <code className=\"font-mono text-xs\">{row.original.code}</code>,\n },\n {\n header: t('attachments.partitions.table.title', 'Title'),\n accessorKey: 'title',\n cell: ({ row }) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium\">{row.original.title}</span>\n {row.original.description ? (\n <span className=\"text-xs text-muted-foreground line-clamp-2\">{row.original.description}</span>\n ) : null}\n </div>\n ),\n },\n {\n header: t('attachments.partitions.table.visibility', 'Visibility'),\n accessorKey: 'isPublic',\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {row.original.isPublic\n ? t('attachments.partitions.table.public', 'Public')\n : t('attachments.partitions.table.private', 'Private')}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.ocr', 'OCR'),\n accessorKey: 'requiresOcr',\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {row.original.requiresOcr\n ? t('common.enabled', 'Enabled')\n : t('common.disabled', 'Disabled')}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.envKey', 'Env variable'),\n accessorKey: 'envKey',\n cell: ({ row }) => <code className=\"text-xs\">{row.original.envKey}</code>,\n },\n ],\n [t],\n )\n\n const tableLabels = React.useMemo(\n () => ({\n search: t('attachments.partitions.table.search', 'Search partitions\u2026'),\n empty: t('attachments.partitions.table.empty', 'No partitions configured.'),\n }),\n [t],\n )\n\n const formKeyHandler = React.useCallback(\n (event: React.KeyboardEvent<HTMLFormElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleSubmit()\n }\n },\n [handleSubmit],\n )\n\n return (\n <div className=\"space-y-6 rounded-lg border bg-card p-6\">\n <div className=\"flex flex-col gap-2 md:flex-row md:items-center md:justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold\">\n {t('attachments.partitions.title', 'Attachment partitions')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('attachments.partitions.description', 'Define storage partitions and visibility for uploads.')}\n </p>\n </div>\n <Button size=\"sm\" onClick={() => openDialog({ mode: 'create' })}>\n {t('attachments.partitions.actions.add', 'Add partition')}\n </Button>\n </div>\n <DataTable<Partition>\n columns={columns}\n data={filteredItems}\n searchValue={search}\n onSearchChange={(value: string) => setSearch(value)}\n searchPlaceholder={tableLabels.search}\n emptyState={<p className=\"py-8 text-center text-sm text-muted-foreground\">{tableLabels.empty}</p>}\n isLoading={loading}\n refreshButton={{\n label: t('attachments.partitions.actions.refresh', 'Refresh'),\n onRefresh: () => { void loadItems() },\n isRefreshing: loading,\n }}\n rowActions={(entry) => (\n <RowActions\n items={[\n {\n id: 'edit',\n label: t('attachments.partitions.actions.edit', 'Edit'),\n onSelect: () => openDialog({ mode: 'edit', entry }),\n },\n {\n id: 'delete',\n label: t('attachments.partitions.actions.delete', 'Delete'),\n destructive: true,\n onSelect: () => { void handleDelete(entry) },\n },\n ]}\n />\n )}\n />\n <Dialog open={dialog !== null} onOpenChange={(open) => { if (!open) closeDialog() }}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.dialog.editTitle', 'Edit partition')\n : t('attachments.partitions.dialog.createTitle', 'Create partition')}\n </DialogTitle>\n <DialogDescription>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.dialog.editDescription', 'Update partition metadata and visibility.')\n : t('attachments.partitions.dialog.createDescription', 'Define a storage partition for attachments.')}\n </DialogDescription>\n </DialogHeader>\n <form\n className=\"space-y-4\"\n onKeyDown={formKeyHandler}\n onSubmit={(event) => {\n event.preventDefault()\n void handleSubmit()\n }}\n >\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-code\">{t('attachments.partitions.form.codeLabel', 'Code')}</Label>\n <Input\n id=\"partition-code\"\n value={form.code}\n onChange={(event) => setForm((prev) => ({ ...prev, code: event.target.value }))}\n placeholder={t('attachments.partitions.form.codePlaceholder', 'e.g. marketingAssets')}\n disabled={dialog?.mode === 'edit'}\n className=\"font-mono uppercase\"\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-title\">{t('attachments.partitions.form.titleLabel', 'Title')}</Label>\n <Input\n id=\"partition-title\"\n value={form.title}\n onChange={(event) => setForm((prev) => ({ ...prev, title: event.target.value }))}\n placeholder={t('attachments.partitions.form.titlePlaceholder', 'e.g. Marketing assets')}\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-description\">{t('attachments.partitions.form.descriptionLabel', 'Description')}</Label>\n <textarea\n id=\"partition-description\"\n className=\"min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.description}\n onChange={(event) => setForm((prev) => ({ ...prev, description: event.target.value }))}\n placeholder={t('attachments.partitions.form.descriptionPlaceholder', 'Explain how this partition is used.')}\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.isPublic}\n onChange={(event) => setForm((prev) => ({ ...prev, isPublic: event.target.checked }))}\n />\n {t('attachments.partitions.form.publicLabel', 'Publicly accessible')}\n </label>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.requiresOcr}\n onChange={(event) => setForm((prev) => ({ ...prev, requiresOcr: event.target.checked }))}\n />\n {t('attachments.partitions.form.ocrLabel', 'Require OCR/text extraction')}\n </label>\n {form.requiresOcr && (\n <div className=\"space-y-2 pl-6\">\n <Label htmlFor=\"partition-ocr-model\">\n {t('attachments.partitions.form.ocrModelLabel', 'OCR Model')}\n </Label>\n <Select\n value={form.ocrModel || undefined}\n onValueChange={(value) => setForm((prev) => ({ ...prev, ocrModel: value ?? '' }))}\n >\n <SelectTrigger id=\"partition-ocr-model\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {OCR_MODEL_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {t(`attachments.partitions.form.ocrModelOptions.${option.value || 'default'}`, option.label)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'attachments.partitions.form.ocrModelHelp',\n 'Choose the LLM model for OCR processing. Falls back to OCR_MODEL environment variable or gpt-4o.'\n )}\n </p>\n </div>\n )}\n {dialog ? (\n <div className=\"rounded-md border bg-muted/50 px-3 py-2 text-xs text-muted-foreground\">\n <div>\n {t('attachments.partitions.form.envKeyHelp', 'Set this env var to override storage path:')}\n </div>\n <code>\n {dialog.mode === 'edit'\n ? dialog.entry.envKey\n : form.code.trim()\n ? resolvePartitionEnvKey(form.code.trim())\n : 'ATTACHMENTS_PARTITION_CODE_ROOT'}\n </code>\n </div>\n ) : null}\n {error ? <p className=\"text-sm text-red-600\">{error}</p> : null}\n </form>\n <DialogFooter>\n <Button variant=\"ghost\" onClick={closeDialog}>\n {t('attachments.partitions.actions.cancel', 'Cancel')}\n </Button>\n <Button onClick={() => void handleSubmit()} disabled={submitting}>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.actions.save', 'Save changes')\n : t('attachments.partitions.actions.create', 'Create partition')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { resolvePartitionEnvKey } from '@open-mercato/core/modules/attachments/lib/partitionEnv'\n\ntype Partition = {\n id: string\n code: string\n title: string\n description: string | null\n isPublic: boolean\n requiresOcr: boolean\n ocrModel: string | null\n storageDriver: string\n configJson: Record<string, unknown> | null\n envKey: string\n createdAt: string | null\n}\n\ntype DialogState =\n | { mode: 'create' }\n | { mode: 'edit'; entry: Partition }\n\ntype S3CredentialsSource = 'integration' | 'env'\n\nconst DEFAULT_FORM = {\n code: '',\n title: '',\n description: '',\n isPublic: false,\n requiresOcr: true,\n ocrModel: '',\n storageDriver: 'local',\n s3CredentialsSource: 'integration' as S3CredentialsSource,\n s3CredentialsEnvPrefix: '',\n s3Bucket: '',\n s3Region: '',\n s3Endpoint: '',\n s3ForcePathStyle: false,\n}\n\nconst ALL_STORAGE_DRIVER_OPTIONS = [\n { value: 'local', label: 'Local filesystem' },\n { value: 's3', label: 'Amazon S3 / S3-compatible', requiresModule: 's3' },\n]\n\nconst OCR_MODEL_DEFAULT_VALUE = '__default__'\n\nconst OCR_MODEL_OPTIONS = [\n { value: OCR_MODEL_DEFAULT_VALUE, label: 'Default (from environment)' },\n { value: 'gpt-4o', label: 'GPT-4o (Recommended)' },\n { value: 'gpt-4o-mini', label: 'GPT-4o Mini (Faster, Lower Cost)' },\n]\n\nexport type AttachmentPartitionSettingsProps = {\n s3Enabled: boolean\n}\n\nexport function AttachmentPartitionSettings({ s3Enabled }: AttachmentPartitionSettingsProps) {\n const storageDriverOptions = ALL_STORAGE_DRIVER_OPTIONS.filter(\n (opt) => !opt.requiresModule || (opt.requiresModule === 's3' && s3Enabled),\n )\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [items, setItems] = React.useState<Partition[]>([])\n const [loading, setLoading] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [dialog, setDialog] = React.useState<DialogState | null>(null)\n const [form, setForm] = React.useState(DEFAULT_FORM)\n const [submitting, setSubmitting] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const s3Unavailable = !s3Enabled\n const s3SelectedWhileUnavailable = s3Unavailable && form.storageDriver === 's3'\n\n const loadErrorMessage = t('attachments.partitions.errors.load', 'Failed to load partitions.')\n\n const loadItems = React.useCallback(async () => {\n setLoading(true)\n try {\n const payload = await readApiResultOrThrow<{ items?: Partition[] }>(\n '/api/attachments/partitions',\n undefined,\n { errorMessage: loadErrorMessage },\n )\n const normalized = Array.isArray(payload.items) ? payload.items : []\n const withDefaults = normalized.map((entry) => ({\n ...entry,\n requiresOcr: typeof entry.requiresOcr === 'boolean' ? entry.requiresOcr : true,\n }))\n setItems(withDefaults)\n } catch (err) {\n console.error('[attachments.partitions] list failed', err)\n flash(loadErrorMessage, 'error')\n } finally {\n setLoading(false)\n }\n }, [loadErrorMessage])\n\n React.useEffect(() => {\n loadItems().catch(() => {})\n }, [loadItems])\n\n const filteredItems = React.useMemo(() => {\n if (!search.trim()) return items\n const term = search.trim().toLowerCase()\n return items.filter(\n (entry) =>\n entry.code.toLowerCase().includes(term) ||\n entry.title.toLowerCase().includes(term) ||\n (entry.description ?? '').toLowerCase().includes(term),\n )\n }, [items, search])\n\n const openDialog = React.useCallback((state: DialogState) => {\n if (state.mode === 'edit') {\n const cfg = state.entry.configJson ?? {}\n const credsPrefix = typeof cfg.credentialsEnvPrefix === 'string' ? cfg.credentialsEnvPrefix : ''\n setForm({\n code: state.entry.code,\n title: state.entry.title,\n description: state.entry.description ?? '',\n isPublic: state.entry.isPublic,\n requiresOcr: state.entry.requiresOcr,\n ocrModel: state.entry.ocrModel ?? '',\n storageDriver: state.entry.storageDriver ?? 'local',\n s3CredentialsSource: credsPrefix ? 'env' : 'integration',\n s3CredentialsEnvPrefix: credsPrefix,\n s3Bucket: typeof cfg.bucket === 'string' ? cfg.bucket : '',\n s3Region: typeof cfg.region === 'string' ? cfg.region : '',\n s3Endpoint: typeof cfg.endpoint === 'string' ? cfg.endpoint : '',\n s3ForcePathStyle: cfg.forcePathStyle === true,\n })\n } else {\n setForm(DEFAULT_FORM)\n }\n setError(null)\n setDialog(state)\n }, [])\n\n const closeDialog = React.useCallback(() => {\n setDialog(null)\n setError(null)\n setSubmitting(false)\n setForm(DEFAULT_FORM)\n }, [])\n\n const handleSubmit = React.useCallback(async () => {\n if (!dialog) return\n const trimmedCode = form.code.trim()\n const trimmedTitle = form.title.trim()\n if (!trimmedCode || !trimmedTitle) {\n setError(t('attachments.partitions.errors.required', 'Code and title are required.'))\n return\n }\n if (form.storageDriver === 's3' && form.s3CredentialsSource === 'env') {\n if (!form.s3CredentialsEnvPrefix.trim()) {\n setError(\n t(\n 'attachments.partitions.errors.s3EnvPrefixRequired',\n 'Credentials env prefix is required when using a separate credentials source.',\n ),\n )\n return\n }\n if (!form.s3Bucket.trim()) {\n setError(\n t(\n 'attachments.partitions.errors.s3BucketRequired',\n 'Bucket is required when using a separate credentials source.',\n ),\n )\n return\n }\n }\n setSubmitting(true)\n setError(null)\n try {\n const s3ConfigJson =\n form.storageDriver === 's3'\n ? {\n bucket: form.s3Bucket.trim() || undefined,\n region: form.s3Region.trim() || undefined,\n endpoint: form.s3Endpoint.trim() || undefined,\n forcePathStyle: form.s3ForcePathStyle || undefined,\n ...(form.s3CredentialsSource === 'env'\n ? { credentialsEnvPrefix: form.s3CredentialsEnvPrefix.trim() || undefined }\n : {}),\n }\n : null\n\n const payload = {\n code: trimmedCode,\n title: trimmedTitle,\n description: form.description.trim() || undefined,\n isPublic: form.isPublic,\n requiresOcr: form.requiresOcr,\n ocrModel: form.ocrModel.trim() || null,\n storageDriver: form.storageDriver,\n configJson: s3ConfigJson,\n }\n const method = dialog.mode === 'create' ? 'POST' : 'PUT'\n const body =\n dialog.mode === 'edit'\n ? JSON.stringify({ id: dialog.entry.id, ...payload })\n : JSON.stringify(payload)\n const call = await apiCall('/api/attachments/partitions', {\n method,\n headers: { 'content-type': 'application/json' },\n body,\n })\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('attachments.partitions.errors.save', 'Failed to save partition.'),\n )\n }\n flash(\n dialog.mode === 'create'\n ? t('attachments.partitions.messages.created', 'Partition created.')\n : t('attachments.partitions.messages.updated', 'Partition updated.'),\n 'success',\n )\n closeDialog()\n await loadItems()\n } catch (err) {\n console.error('[attachments.partitions] save failed', err)\n const message =\n err instanceof Error ? err.message : t('attachments.partitions.errors.save', 'Failed to save partition.')\n setError(message)\n } finally {\n setSubmitting(false)\n }\n }, [dialog, form, t, closeDialog, loadItems])\n\n const handleDelete = React.useCallback(\n async (entry: Partition) => {\n const confirmMessage = t('attachments.partitions.confirm.delete', 'Delete partition \"{{code}}\"?').replace(\n '{{code}}',\n entry.code,\n )\n const confirmed = await confirm({\n title: confirmMessage,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const call = await apiCall(`/api/attachments/partitions?id=${encodeURIComponent(entry.id)}`, {\n method: 'DELETE',\n })\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('attachments.partitions.errors.delete', 'Failed to delete partition.'),\n )\n }\n flash(t('attachments.partitions.messages.deleted', 'Partition removed.'), 'success')\n await loadItems()\n } catch (err) {\n console.error('[attachments.partitions] delete failed', err)\n flash(t('attachments.partitions.errors.delete', 'Failed to delete partition.'), 'error')\n }\n },\n [confirm, loadItems, t],\n )\n\n const columns = React.useMemo<ColumnDef<Partition>[]>(\n () => [\n {\n header: t('attachments.partitions.table.code', 'Code'),\n accessorKey: 'code',\n cell: ({ row }) => <code className=\"font-mono text-xs\">{row.original.code}</code>,\n },\n {\n header: t('attachments.partitions.table.title', 'Title'),\n accessorKey: 'title',\n cell: ({ row }) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium\">{row.original.title}</span>\n {row.original.description ? (\n <span className=\"text-xs text-muted-foreground line-clamp-2\">{row.original.description}</span>\n ) : null}\n </div>\n ),\n },\n {\n header: t('attachments.partitions.table.visibility', 'Visibility'),\n accessorKey: 'isPublic',\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {row.original.isPublic\n ? t('attachments.partitions.table.public', 'Public')\n : t('attachments.partitions.table.private', 'Private')}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.ocr', 'OCR'),\n accessorKey: 'requiresOcr',\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {row.original.requiresOcr\n ? t('common.enabled', 'Enabled')\n : t('common.disabled', 'Disabled')}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.storageDriver', 'Storage'),\n accessorKey: 'storageDriver',\n cell: ({ row }) => (\n <span className=\"text-sm capitalize\">\n {row.original.storageDriver === 's3' ? 'S3' : row.original.storageDriver ?? 'local'}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.envKey', 'Env variable'),\n accessorKey: 'envKey',\n cell: ({ row }) => <code className=\"text-xs\">{row.original.envKey}</code>,\n },\n ],\n [t],\n )\n\n const tableLabels = React.useMemo(\n () => ({\n search: t('attachments.partitions.table.search', 'Search partitions\u2026'),\n empty: t('attachments.partitions.table.empty', 'No partitions configured.'),\n }),\n [t],\n )\n\n const formKeyHandler = React.useCallback(\n (event: React.KeyboardEvent<HTMLFormElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleSubmit()\n }\n },\n [handleSubmit],\n )\n\n return (\n <div className=\"space-y-6 rounded-lg border bg-card p-6\">\n <div className=\"flex flex-col gap-2 md:flex-row md:items-center md:justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold\">\n {t('attachments.partitions.title', 'Attachment partitions')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('attachments.partitions.description', 'Define storage partitions and visibility for uploads.')}\n </p>\n </div>\n <Button size=\"sm\" onClick={() => openDialog({ mode: 'create' })}>\n {t('attachments.partitions.actions.add', 'Add partition')}\n </Button>\n </div>\n <DataTable<Partition>\n columns={columns}\n data={filteredItems}\n searchValue={search}\n onSearchChange={(value: string) => setSearch(value)}\n searchPlaceholder={tableLabels.search}\n emptyState={<p className=\"py-8 text-center text-sm text-muted-foreground\">{tableLabels.empty}</p>}\n isLoading={loading}\n refreshButton={{\n label: t('attachments.partitions.actions.refresh', 'Refresh'),\n onRefresh: () => { void loadItems() },\n isRefreshing: loading,\n }}\n rowActions={(entry) => (\n <RowActions\n items={[\n {\n id: 'edit',\n label: t('attachments.partitions.actions.edit', 'Edit'),\n onSelect: () => openDialog({ mode: 'edit', entry }),\n },\n {\n id: 'delete',\n label: t('attachments.partitions.actions.delete', 'Delete'),\n destructive: true,\n onSelect: () => { void handleDelete(entry) },\n },\n ]}\n />\n )}\n />\n <Dialog open={dialog !== null} onOpenChange={(open) => { if (!open) closeDialog() }}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.dialog.editTitle', 'Edit partition')\n : t('attachments.partitions.dialog.createTitle', 'Create partition')}\n </DialogTitle>\n <DialogDescription>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.dialog.editDescription', 'Update partition metadata and visibility.')\n : t('attachments.partitions.dialog.createDescription', 'Define a storage partition for attachments.')}\n </DialogDescription>\n </DialogHeader>\n <form\n className=\"space-y-4\"\n onKeyDown={formKeyHandler}\n onSubmit={(event) => {\n event.preventDefault()\n void handleSubmit()\n }}\n >\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-code\">{t('attachments.partitions.form.codeLabel', 'Code')}</Label>\n <Input\n id=\"partition-code\"\n value={form.code}\n onChange={(event) => setForm((prev) => ({ ...prev, code: event.target.value }))}\n placeholder={t('attachments.partitions.form.codePlaceholder', 'e.g. marketingAssets')}\n disabled={dialog?.mode === 'edit'}\n className=\"font-mono uppercase\"\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-title\">{t('attachments.partitions.form.titleLabel', 'Title')}</Label>\n <Input\n id=\"partition-title\"\n value={form.title}\n onChange={(event) => setForm((prev) => ({ ...prev, title: event.target.value }))}\n placeholder={t('attachments.partitions.form.titlePlaceholder', 'e.g. Marketing assets')}\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-description\">{t('attachments.partitions.form.descriptionLabel', 'Description')}</Label>\n <textarea\n id=\"partition-description\"\n className=\"min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.description}\n onChange={(event) => setForm((prev) => ({ ...prev, description: event.target.value }))}\n placeholder={t('attachments.partitions.form.descriptionPlaceholder', 'Explain how this partition is used.')}\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.isPublic}\n onChange={(event) => setForm((prev) => ({ ...prev, isPublic: event.target.checked }))}\n />\n {t('attachments.partitions.form.publicLabel', 'Publicly accessible')}\n </label>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.requiresOcr}\n onChange={(event) => setForm((prev) => ({ ...prev, requiresOcr: event.target.checked }))}\n />\n {t('attachments.partitions.form.ocrLabel', 'Require OCR/text extraction')}\n </label>\n {form.requiresOcr && (\n <div className=\"space-y-2 pl-6\">\n <Label htmlFor=\"partition-ocr-model\">\n {t('attachments.partitions.form.ocrModelLabel', 'OCR Model')}\n </Label>\n <Select\n value={form.ocrModel ? form.ocrModel : OCR_MODEL_DEFAULT_VALUE}\n onValueChange={(value) =>\n setForm((prev) => ({\n ...prev,\n ocrModel: value === OCR_MODEL_DEFAULT_VALUE ? '' : value,\n }))\n }\n >\n <SelectTrigger id=\"partition-ocr-model\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {OCR_MODEL_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {t(\n `attachments.partitions.form.ocrModelOptions.${\n option.value === OCR_MODEL_DEFAULT_VALUE ? 'default' : option.value\n }`,\n option.label,\n )}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'attachments.partitions.form.ocrModelHelp',\n 'Choose the LLM model for OCR processing. Falls back to OCR_MODEL environment variable or gpt-4o.'\n )}\n </p>\n </div>\n )}\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-storage-driver\">\n {t('attachments.partitions.form.storageDriverLabel', 'Storage Driver')}\n </Label>\n <select\n id=\"partition-storage-driver\"\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.storageDriver}\n onChange={(event) => setForm((prev) => ({ ...prev, storageDriver: event.target.value }))}\n >\n {storageDriverOptions.map((option) => (\n <option\n key={option.value}\n value={option.value}\n disabled={option.value === 's3' && s3Unavailable}\n >\n {option.label}\n </option>\n ))}\n </select>\n {s3Unavailable ? (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'attachments.partitions.form.storageDriverS3DisabledHelp',\n 'S3 is disabled. Set OM_ENABLE_STORAGE_S3=true and restart to enable S3 partitions.',\n )}\n </p>\n ) : null}\n </div>\n {form.storageDriver === 's3' && (\n <div className=\"space-y-3 rounded-md border bg-muted/30 p-3\">\n <p className=\"text-xs font-medium text-muted-foreground\">\n {t('attachments.partitions.form.s3ConfigTitle', 'S3 Configuration')}\n </p>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-creds-source\">\n {t('attachments.partitions.form.s3CredsSourceLabel', 'Credentials source')}\n </Label>\n <select\n id=\"partition-s3-creds-source\"\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.s3CredentialsSource}\n onChange={(event) =>\n setForm((prev) => ({\n ...prev,\n s3CredentialsSource: event.target.value as S3CredentialsSource,\n ...(event.target.value === 'integration' ? { s3CredentialsEnvPrefix: '' } : {}),\n }))\n }\n >\n <option value=\"integration\">\n {t(\n 'attachments.partitions.form.s3CredsSourceIntegration',\n 'Use Integration Marketplace credentials',\n )}\n </option>\n <option value=\"env\">\n {t(\n 'attachments.partitions.form.s3CredsSourceEnv',\n 'Use separate env-based credentials',\n )}\n </option>\n </select>\n <p className=\"text-xs text-muted-foreground\">\n {form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3CredsSourceIntegrationHelp',\n 'This partition reuses the credentials, bucket, region and endpoint configured in the S3 integration. Override individual fields below only when needed.',\n )\n : t(\n 'attachments.partitions.form.s3CredsSourceEnvHelp',\n 'This partition uses its own credentials read from environment variables. The integration credentials are ignored. Bucket and credentials prefix are required.',\n )}\n </p>\n </div>\n {form.s3CredentialsSource === 'env' && (\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-creds-prefix\">\n {t('attachments.partitions.form.s3CredsPrefixLabel', 'Credentials Env Prefix')}\n </Label>\n <Input\n id=\"partition-s3-creds-prefix\"\n value={form.s3CredentialsEnvPrefix}\n onChange={(event) => setForm((prev) => ({ ...prev, s3CredentialsEnvPrefix: event.target.value }))}\n placeholder=\"OM_INTEGRATION_STORAGE_S3\"\n className=\"font-mono\"\n />\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'attachments.partitions.form.s3CredsPrefixHelp',\n 'Reads {PREFIX}_ACCESS_KEY_ID and {PREFIX}_SECRET_ACCESS_KEY from environment variables.',\n )}\n </p>\n </div>\n )}\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-bucket\">\n {form.s3CredentialsSource === 'integration'\n ? t('attachments.partitions.form.s3BucketOverrideLabel', 'Bucket override (optional)')\n : t('attachments.partitions.form.s3BucketLabel', 'Bucket')}\n </Label>\n <Input\n id=\"partition-s3-bucket\"\n value={form.s3Bucket}\n onChange={(event) => setForm((prev) => ({ ...prev, s3Bucket: event.target.value }))}\n placeholder={\n form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3BucketOverridePlaceholder',\n 'Leave empty to use integration bucket',\n )\n : 'my-company-attachments'\n }\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-region\">\n {form.s3CredentialsSource === 'integration'\n ? t('attachments.partitions.form.s3RegionOverrideLabel', 'Region override (optional)')\n : t('attachments.partitions.form.s3RegionLabel', 'Region')}\n </Label>\n <Input\n id=\"partition-s3-region\"\n value={form.s3Region}\n onChange={(event) => setForm((prev) => ({ ...prev, s3Region: event.target.value }))}\n placeholder={\n form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3RegionOverridePlaceholder',\n 'Leave empty to use integration region',\n )\n : 'us-east-1'\n }\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-endpoint\">\n {form.s3CredentialsSource === 'integration'\n ? t('attachments.partitions.form.s3EndpointOverrideLabel', 'Custom endpoint override (optional)')\n : t('attachments.partitions.form.s3EndpointLabel', 'Custom Endpoint')}\n </Label>\n <Input\n id=\"partition-s3-endpoint\"\n value={form.s3Endpoint}\n onChange={(event) => setForm((prev) => ({ ...prev, s3Endpoint: event.target.value }))}\n placeholder={\n form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3EndpointOverridePlaceholder',\n 'Leave empty to use integration endpoint',\n )\n : 'https://fra1.digitaloceanspaces.com'\n }\n />\n <p className=\"text-xs text-muted-foreground\">\n {t('attachments.partitions.form.s3EndpointHelp', 'Leave empty for AWS S3. Required for MinIO, DigitalOcean Spaces, etc.')}\n </p>\n </div>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.s3ForcePathStyle}\n onChange={(event) => setForm((prev) => ({ ...prev, s3ForcePathStyle: event.target.checked }))}\n />\n {t('attachments.partitions.form.s3ForcePathStyleLabel', 'Force Path Style (required for MinIO)')}\n </label>\n </div>\n )}\n {dialog ? (\n <div className=\"rounded-md border bg-muted/50 px-3 py-2 text-xs text-muted-foreground\">\n <div>\n {t('attachments.partitions.form.envKeyHelp', 'Set this env var to override storage path:')}\n </div>\n <code>\n {dialog.mode === 'edit'\n ? dialog.entry.envKey\n : form.code.trim()\n ? resolvePartitionEnvKey(form.code.trim())\n : 'ATTACHMENTS_PARTITION_CODE_ROOT'}\n </code>\n </div>\n ) : null}\n {s3SelectedWhileUnavailable ? (\n <p className=\"text-sm text-status-error-text\">\n {t(\n 'attachments.partitions.errors.s3Disabled',\n 'This partition uses S3, but S3 is disabled. Set OM_ENABLE_STORAGE_S3=true and restart the app.',\n )}\n </p>\n ) : null}\n {error ? <p className=\"text-sm text-status-error-text\">{error}</p> : null}\n </form>\n <DialogFooter>\n <Button variant=\"ghost\" onClick={closeDialog}>\n {t('attachments.partitions.actions.cancel', 'Cancel')}\n </Button>\n <Button onClick={() => void handleSubmit()} disabled={submitting || s3SelectedWhileUnavailable}>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.actions.save', 'Save changes')\n : t('attachments.partitions.actions.create', 'Create partition')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,13 @@
1
+ import { asFunction } from "awilix";
2
+ import { StorageDriverFactory } from "./lib/drivers/driverFactory.js";
3
+ function register(container) {
4
+ container.register({
5
+ storageDriverFactory: asFunction(
6
+ ({ em }) => new StorageDriverFactory(em)
7
+ ).singleton().proxy()
8
+ });
9
+ }
10
+ export {
11
+ register
12
+ };
13
+ //# sourceMappingURL=di.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/attachments/di.ts"],
4
+ "sourcesContent": ["import { asFunction } from 'awilix'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { StorageDriverFactory } from './lib/drivers/driverFactory'\n\nexport function register(container: AppContainer) {\n container.register({\n storageDriverFactory: asFunction(({ em }: { em: ConstructorParameters<typeof StorageDriverFactory>[0] }) =>\n new StorageDriverFactory(em),\n )\n .singleton()\n .proxy(),\n })\n}\n"],
5
+ "mappings": "AAAA,SAAS,kBAAkB;AAE3B,SAAS,4BAA4B;AAE9B,SAAS,SAAS,WAAyB;AAChD,YAAU,SAAS;AAAA,IACjB,sBAAsB;AAAA,MAAW,CAAC,EAAE,GAAG,MACrC,IAAI,qBAAqB,EAAE;AAAA,IAC7B,EACG,UAAU,EACV,MAAM;AAAA,EACX,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -0,0 +1,67 @@
1
+ import { LocalStorageDriver } from "./localDriver.js";
2
+ import { LegacyPublicStorageDriver } from "./legacyPublicDriver.js";
3
+ import { AttachmentPartition } from "../../data/entities.js";
4
+ const moduleDriverRegistry = /* @__PURE__ */ new Map();
5
+ const moduleEnhancerRegistry = /* @__PURE__ */ new Map();
6
+ function registerExternalStorageDriver(key, factory) {
7
+ moduleDriverRegistry.set(key, { factory });
8
+ }
9
+ function registerExternalCredentialEnhancer(key, enhancer) {
10
+ moduleEnhancerRegistry.set(key, { enhancer });
11
+ }
12
+ class StorageDriverFactory {
13
+ constructor(em) {
14
+ this.em = em;
15
+ this.cache = /* @__PURE__ */ new Map();
16
+ this.localDriver = new LocalStorageDriver();
17
+ this.legacyPublicDriver = new LegacyPublicStorageDriver();
18
+ this.externalDrivers = /* @__PURE__ */ new Map();
19
+ this.credentialEnhancers = /* @__PURE__ */ new Map();
20
+ }
21
+ registerDriver(key, factory) {
22
+ this.externalDrivers.set(key, factory);
23
+ }
24
+ registerCredentialEnhancer(key, enhancer) {
25
+ this.credentialEnhancers.set(key, enhancer);
26
+ }
27
+ resolveForAttachment(storageDriver, configJson) {
28
+ switch (storageDriver) {
29
+ case "legacyPublic":
30
+ return this.legacyPublicDriver;
31
+ case "local":
32
+ return this.localDriver;
33
+ default: {
34
+ const externalFactory = this.externalDrivers.get(storageDriver) ?? moduleDriverRegistry.get(storageDriver)?.factory;
35
+ if (externalFactory) {
36
+ const cacheKey = `${storageDriver}:${JSON.stringify(configJson ?? {})}`;
37
+ const cached = this.cache.get(cacheKey);
38
+ if (cached) return cached;
39
+ const driver = externalFactory(configJson ?? {});
40
+ this.cache.set(cacheKey, driver);
41
+ return driver;
42
+ }
43
+ return this.localDriver;
44
+ }
45
+ }
46
+ }
47
+ async resolveForPartition(partitionCode, scope) {
48
+ const partition = await this.em.findOne(AttachmentPartition, { code: partitionCode });
49
+ if (!partition) return this.localDriver;
50
+ const driverKey = partition.storageDriver ?? "local";
51
+ console.log(`[storageDriverFactory] resolveForPartition: partition=${partitionCode} driverKey=${driverKey} moduleDrivers=${[...moduleDriverRegistry.keys()].join(",")} instanceDrivers=${[...this.externalDrivers.keys()].join(",")}`);
52
+ let config = partition.configJson ?? {};
53
+ const activeEnhancer = this.credentialEnhancers.get(driverKey) ?? moduleEnhancerRegistry.get(driverKey)?.enhancer;
54
+ if (scope && activeEnhancer) {
55
+ config = await activeEnhancer(config, scope);
56
+ const externalFactory = this.externalDrivers.get(driverKey) ?? moduleDriverRegistry.get(driverKey)?.factory;
57
+ if (externalFactory) return externalFactory(config);
58
+ }
59
+ return this.resolveForAttachment(driverKey, config);
60
+ }
61
+ }
62
+ export {
63
+ StorageDriverFactory,
64
+ registerExternalCredentialEnhancer,
65
+ registerExternalStorageDriver
66
+ };
67
+ //# sourceMappingURL=driverFactory.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/attachments/lib/drivers/driverFactory.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { StorageDriver } from './types'\nimport { LocalStorageDriver } from './localDriver'\nimport { LegacyPublicStorageDriver } from './legacyPublicDriver'\nimport { AttachmentPartition } from '../../data/entities'\n\ntype DriverScope = { tenantId: string; organizationId: string }\ntype CredentialEnhancer = (config: Record<string, unknown>, scope: DriverScope) => Promise<Record<string, unknown>>\n\ntype DriverRegistration = { factory: (config: Record<string, unknown>) => StorageDriver }\ntype EnhancerRegistration = { enhancer: CredentialEnhancer }\n\nconst moduleDriverRegistry = new Map<string, DriverRegistration>()\nconst moduleEnhancerRegistry = new Map<string, EnhancerRegistration>()\n\nexport function registerExternalStorageDriver(\n key: string,\n factory: (config: Record<string, unknown>) => StorageDriver,\n): void {\n moduleDriverRegistry.set(key, { factory })\n}\n\nexport function registerExternalCredentialEnhancer(key: string, enhancer: CredentialEnhancer): void {\n moduleEnhancerRegistry.set(key, { enhancer })\n}\n\nexport class StorageDriverFactory {\n private readonly cache = new Map<string, StorageDriver>()\n private readonly localDriver = new LocalStorageDriver()\n private readonly legacyPublicDriver = new LegacyPublicStorageDriver()\n private readonly externalDrivers = new Map<string, (config: Record<string, unknown>) => StorageDriver>()\n private readonly credentialEnhancers = new Map<string, CredentialEnhancer>()\n\n constructor(private readonly em: EntityManager) {}\n\n registerDriver(key: string, factory: (config: Record<string, unknown>) => StorageDriver): void {\n this.externalDrivers.set(key, factory)\n }\n\n registerCredentialEnhancer(key: string, enhancer: CredentialEnhancer): void {\n this.credentialEnhancers.set(key, enhancer)\n }\n\n resolveForAttachment(\n storageDriver: string,\n configJson?: Record<string, unknown> | null,\n ): StorageDriver {\n switch (storageDriver) {\n case 'legacyPublic':\n return this.legacyPublicDriver\n\n case 'local':\n return this.localDriver\n\n default: {\n const externalFactory =\n this.externalDrivers.get(storageDriver) ?? moduleDriverRegistry.get(storageDriver)?.factory\n if (externalFactory) {\n const cacheKey = `${storageDriver}:${JSON.stringify(configJson ?? {})}`\n const cached = this.cache.get(cacheKey)\n if (cached) return cached\n const driver = externalFactory(configJson ?? {})\n this.cache.set(cacheKey, driver)\n return driver\n }\n return this.localDriver\n }\n }\n }\n\n async resolveForPartition(partitionCode: string, scope?: DriverScope): Promise<StorageDriver> {\n const partition = await this.em.findOne(AttachmentPartition, { code: partitionCode })\n if (!partition) return this.localDriver\n\n const driverKey = partition.storageDriver ?? 'local'\n console.log(`[storageDriverFactory] resolveForPartition: partition=${partitionCode} driverKey=${driverKey} moduleDrivers=${[...moduleDriverRegistry.keys()].join(',')} instanceDrivers=${[...this.externalDrivers.keys()].join(',')}`)\n let config: Record<string, unknown> = partition.configJson ?? {}\n\n const activeEnhancer =\n this.credentialEnhancers.get(driverKey) ?? moduleEnhancerRegistry.get(driverKey)?.enhancer\n if (scope && activeEnhancer) {\n config = await activeEnhancer(config, scope)\n // Skip shared cache for scope-enhanced configs (credentials are per-tenant)\n const externalFactory =\n this.externalDrivers.get(driverKey) ?? moduleDriverRegistry.get(driverKey)?.factory\n if (externalFactory) return externalFactory(config)\n }\n\n return this.resolveForAttachment(driverKey, config)\n }\n}\n"],
5
+ "mappings": "AAEA,SAAS,0BAA0B;AACnC,SAAS,iCAAiC;AAC1C,SAAS,2BAA2B;AAQpC,MAAM,uBAAuB,oBAAI,IAAgC;AACjE,MAAM,yBAAyB,oBAAI,IAAkC;AAE9D,SAAS,8BACd,KACA,SACM;AACN,uBAAqB,IAAI,KAAK,EAAE,QAAQ,CAAC;AAC3C;AAEO,SAAS,mCAAmC,KAAa,UAAoC;AAClG,yBAAuB,IAAI,KAAK,EAAE,SAAS,CAAC;AAC9C;AAEO,MAAM,qBAAqB;AAAA,EAOhC,YAA6B,IAAmB;AAAnB;AAN7B,SAAiB,QAAQ,oBAAI,IAA2B;AACxD,SAAiB,cAAc,IAAI,mBAAmB;AACtD,SAAiB,qBAAqB,IAAI,0BAA0B;AACpE,SAAiB,kBAAkB,oBAAI,IAAgE;AACvG,SAAiB,sBAAsB,oBAAI,IAAgC;AAAA,EAE1B;AAAA,EAEjD,eAAe,KAAa,SAAmE;AAC7F,SAAK,gBAAgB,IAAI,KAAK,OAAO;AAAA,EACvC;AAAA,EAEA,2BAA2B,KAAa,UAAoC;AAC1E,SAAK,oBAAoB,IAAI,KAAK,QAAQ;AAAA,EAC5C;AAAA,EAEA,qBACE,eACA,YACe;AACf,YAAQ,eAAe;AAAA,MACrB,KAAK;AACH,eAAO,KAAK;AAAA,MAEd,KAAK;AACH,eAAO,KAAK;AAAA,MAEd,SAAS;AACP,cAAM,kBACJ,KAAK,gBAAgB,IAAI,aAAa,KAAK,qBAAqB,IAAI,aAAa,GAAG;AACtF,YAAI,iBAAiB;AACnB,gBAAM,WAAW,GAAG,aAAa,IAAI,KAAK,UAAU,cAAc,CAAC,CAAC,CAAC;AACrE,gBAAM,SAAS,KAAK,MAAM,IAAI,QAAQ;AACtC,cAAI,OAAQ,QAAO;AACnB,gBAAM,SAAS,gBAAgB,cAAc,CAAC,CAAC;AAC/C,eAAK,MAAM,IAAI,UAAU,MAAM;AAC/B,iBAAO;AAAA,QACT;AACA,eAAO,KAAK;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,oBAAoB,eAAuB,OAA6C;AAC5F,UAAM,YAAY,MAAM,KAAK,GAAG,QAAQ,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACpF,QAAI,CAAC,UAAW,QAAO,KAAK;AAE5B,UAAM,YAAY,UAAU,iBAAiB;AAC7C,YAAQ,IAAI,yDAAyD,aAAa,cAAc,SAAS,kBAAkB,CAAC,GAAG,qBAAqB,KAAK,CAAC,EAAE,KAAK,GAAG,CAAC,oBAAoB,CAAC,GAAG,KAAK,gBAAgB,KAAK,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE;AACrO,QAAI,SAAkC,UAAU,cAAc,CAAC;AAE/D,UAAM,iBACJ,KAAK,oBAAoB,IAAI,SAAS,KAAK,uBAAuB,IAAI,SAAS,GAAG;AACpF,QAAI,SAAS,gBAAgB;AAC3B,eAAS,MAAM,eAAe,QAAQ,KAAK;AAE3C,YAAM,kBACJ,KAAK,gBAAgB,IAAI,SAAS,KAAK,qBAAqB,IAAI,SAAS,GAAG;AAC9E,UAAI,gBAAiB,QAAO,gBAAgB,MAAM;AAAA,IACpD;AAEA,WAAO,KAAK,qBAAqB,WAAW,MAAM;AAAA,EACpD;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,11 @@
1
+ import { LocalStorageDriver } from "./localDriver.js";
2
+ import { LegacyPublicStorageDriver } from "./legacyPublicDriver.js";
3
+ import { StorageDriverFactory, registerExternalStorageDriver, registerExternalCredentialEnhancer } from "./driverFactory.js";
4
+ export {
5
+ LegacyPublicStorageDriver,
6
+ LocalStorageDriver,
7
+ StorageDriverFactory,
8
+ registerExternalCredentialEnhancer,
9
+ registerExternalStorageDriver
10
+ };
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/attachments/lib/drivers/index.ts"],
4
+ "sourcesContent": ["export type { StorageDriver, StoreFilePayload, StoredFile, ReadFileResult } from './types'\nexport { LocalStorageDriver } from './localDriver'\nexport { LegacyPublicStorageDriver } from './legacyPublicDriver'\nexport { StorageDriverFactory, registerExternalStorageDriver, registerExternalCredentialEnhancer } from './driverFactory'\n"],
5
+ "mappings": "AACA,SAAS,0BAA0B;AACnC,SAAS,iCAAiC;AAC1C,SAAS,sBAAsB,+BAA+B,0CAA0C;",
6
+ "names": []
7
+ }