@open-mercato/core 0.6.2-develop.3380.1.bb14509041 → 0.6.2-develop.3400.1.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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/attachments/api/file/[id]/route.js +8 -8
- package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +7 -8
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
- package/dist/modules/attachments/api/library/[id]/route.js +6 -2
- package/dist/modules/attachments/api/library/[id]/route.js.map +2 -2
- package/dist/modules/attachments/api/library/route.js +2 -0
- package/dist/modules/attachments/api/library/route.js.map +2 -2
- package/dist/modules/attachments/api/openapi.js +5 -1
- package/dist/modules/attachments/api/openapi.js.map +2 -2
- package/dist/modules/attachments/api/partitions/route.js +10 -1
- package/dist/modules/attachments/api/partitions/route.js.map +2 -2
- package/dist/modules/attachments/api/route.js +19 -7
- package/dist/modules/attachments/api/route.js.map +2 -2
- package/dist/modules/attachments/backend/config/attachments/page.js +3 -1
- package/dist/modules/attachments/backend/config/attachments/page.js.map +2 -2
- package/dist/modules/attachments/cli.js +6 -2
- package/dist/modules/attachments/cli.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentPartitionSettings.js +216 -10
- package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +2 -2
- package/dist/modules/attachments/di.js +13 -0
- package/dist/modules/attachments/di.js.map +7 -0
- package/dist/modules/attachments/lib/drivers/driverFactory.js +67 -0
- package/dist/modules/attachments/lib/drivers/driverFactory.js.map +7 -0
- package/dist/modules/attachments/lib/drivers/index.js +11 -0
- package/dist/modules/attachments/lib/drivers/index.js.map +7 -0
- package/dist/modules/attachments/lib/drivers/legacyPublicDriver.js +42 -0
- package/dist/modules/attachments/lib/drivers/legacyPublicDriver.js.map +7 -0
- package/dist/modules/attachments/lib/drivers/localDriver.js +70 -0
- package/dist/modules/attachments/lib/drivers/localDriver.js.map +7 -0
- package/dist/modules/attachments/lib/drivers/types.js +1 -0
- package/dist/modules/attachments/lib/drivers/types.js.map +7 -0
- package/dist/modules/attachments/lib/ocrQueue.js +10 -5
- package/dist/modules/attachments/lib/ocrQueue.js.map +2 -2
- package/dist/modules/attachments/lib/storage.js.map +2 -2
- package/dist/modules/attachments/lib/thumbnailCache.js +11 -13
- package/dist/modules/attachments/lib/thumbnailCache.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +31 -5
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +14 -2
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
- package/package.json +11 -7
- package/src/modules/attachments/api/file/[id]/route.ts +10 -8
- package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +8 -8
- package/src/modules/attachments/api/library/[id]/route.ts +7 -3
- package/src/modules/attachments/api/library/route.ts +2 -0
- package/src/modules/attachments/api/openapi.ts +4 -0
- package/src/modules/attachments/api/partitions/route.ts +10 -1
- package/src/modules/attachments/api/route.ts +20 -7
- package/src/modules/attachments/backend/config/attachments/page.tsx +3 -1
- package/src/modules/attachments/cli.ts +7 -2
- package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +275 -7
- package/src/modules/attachments/di.ts +13 -0
- package/src/modules/attachments/lib/drivers/driverFactory.ts +91 -0
- package/src/modules/attachments/lib/drivers/index.ts +4 -0
- package/src/modules/attachments/lib/drivers/legacyPublicDriver.ts +46 -0
- package/src/modules/attachments/lib/drivers/localDriver.ts +79 -0
- package/src/modules/attachments/lib/drivers/types.ts +28 -0
- package/src/modules/attachments/lib/ocrQueue.ts +14 -6
- package/src/modules/attachments/lib/storage.ts +13 -0
- package/src/modules/attachments/lib/thumbnailCache.ts +25 -19
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +30 -4
- package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +15 -2
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
2
|
import { Attachment, AttachmentPartition } from '../data/entities'
|
|
3
3
|
import { OcrService } from './ocrService'
|
|
4
|
+
import type { StorageDriver } from './drivers/types'
|
|
4
5
|
|
|
5
6
|
export type OcrRequestedEvent = {
|
|
6
7
|
attachmentId: string
|
|
7
|
-
|
|
8
|
+
storagePath: string
|
|
8
9
|
mimeType: string
|
|
9
10
|
partitionCode: string
|
|
10
11
|
organizationId: string | null
|
|
@@ -13,13 +14,15 @@ export type OcrRequestedEvent = {
|
|
|
13
14
|
|
|
14
15
|
export async function processAttachmentOcr(
|
|
15
16
|
em: EntityManager,
|
|
16
|
-
payload: OcrRequestedEvent
|
|
17
|
+
payload: OcrRequestedEvent,
|
|
18
|
+
driver: StorageDriver,
|
|
17
19
|
): Promise<void> {
|
|
18
|
-
const { attachmentId,
|
|
20
|
+
const { attachmentId, storagePath, mimeType, partitionCode } = payload
|
|
19
21
|
|
|
20
22
|
console.log(`[attachments.ocr] Processing started for attachment: ${attachmentId}`)
|
|
21
23
|
const startTime = Date.now()
|
|
22
24
|
|
|
25
|
+
const { filePath, cleanup } = await driver.toLocalPath(partitionCode, storagePath)
|
|
23
26
|
try {
|
|
24
27
|
const partition = await em.findOne(AttachmentPartition, { code: partitionCode })
|
|
25
28
|
const resolvedModel = partition?.ocrModel ?? process.env.OCR_MODEL ?? 'gpt-4o'
|
|
@@ -63,17 +66,22 @@ export async function processAttachmentOcr(
|
|
|
63
66
|
attachmentId,
|
|
64
67
|
error: error instanceof Error ? error.message : String(error),
|
|
65
68
|
})
|
|
69
|
+
} finally {
|
|
70
|
+
await cleanup().catch((cleanupError) => {
|
|
71
|
+
console.warn(`[attachments.ocr] Temp file cleanup failed:`, cleanupError)
|
|
72
|
+
})
|
|
66
73
|
}
|
|
67
74
|
}
|
|
68
75
|
|
|
69
76
|
export async function requestOcrProcessing(
|
|
70
77
|
em: EntityManager,
|
|
71
78
|
attachment: Attachment,
|
|
72
|
-
|
|
79
|
+
driver: StorageDriver,
|
|
80
|
+
storagePath: string,
|
|
73
81
|
): Promise<void> {
|
|
74
82
|
const payload: OcrRequestedEvent = {
|
|
75
83
|
attachmentId: attachment.id,
|
|
76
|
-
|
|
84
|
+
storagePath,
|
|
77
85
|
mimeType: attachment.mimeType,
|
|
78
86
|
partitionCode: attachment.partitionCode,
|
|
79
87
|
organizationId: attachment.organizationId ?? null,
|
|
@@ -82,7 +90,7 @@ export async function requestOcrProcessing(
|
|
|
82
90
|
|
|
83
91
|
setImmediate(() => {
|
|
84
92
|
const workerEm = typeof (em as any)?.fork === 'function' ? (em as any).fork() : em
|
|
85
|
-
processAttachmentOcr(workerEm, payload).catch((error) => {
|
|
93
|
+
processAttachmentOcr(workerEm, payload, driver).catch((error) => {
|
|
86
94
|
console.error(`[attachments.ocr] Background processing error:`, error)
|
|
87
95
|
})
|
|
88
96
|
})
|
|
@@ -27,6 +27,10 @@ function resolveTenantSegment(tenantId: string | null | undefined): string {
|
|
|
27
27
|
return 'tenant_shared'
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* @deprecated Use `StorageDriverFactory.resolveForPartition()` + `driver.store()` instead.
|
|
32
|
+
* Kept for backward compatibility with external callers.
|
|
33
|
+
*/
|
|
30
34
|
export type StorePartitionFilePayload = {
|
|
31
35
|
partitionCode: string
|
|
32
36
|
orgId: string | null | undefined
|
|
@@ -41,6 +45,9 @@ export type StoredPartitionFile = {
|
|
|
41
45
|
fileName: string
|
|
42
46
|
}
|
|
43
47
|
|
|
48
|
+
/**
|
|
49
|
+
* @deprecated Use `StorageDriverFactory.resolveForPartition()` + `driver.store()` instead.
|
|
50
|
+
*/
|
|
44
51
|
export async function storePartitionFile(payload: StorePartitionFilePayload): Promise<StoredPartitionFile> {
|
|
45
52
|
const root = resolvePartitionRoot(payload.partitionCode)
|
|
46
53
|
const orgSegment = resolveOrgSegment(payload.orgId ?? null)
|
|
@@ -59,6 +66,9 @@ export async function storePartitionFile(payload: StorePartitionFilePayload): Pr
|
|
|
59
66
|
}
|
|
60
67
|
}
|
|
61
68
|
|
|
69
|
+
/**
|
|
70
|
+
* @deprecated Use `StorageDriverFactory.resolveForAttachment()` + `driver.read()` / `driver.toLocalPath()` instead.
|
|
71
|
+
*/
|
|
62
72
|
export function resolveAttachmentAbsolutePath(
|
|
63
73
|
partitionCode: string,
|
|
64
74
|
storagePath: string,
|
|
@@ -78,6 +88,9 @@ export function resolveAttachmentAbsolutePath(
|
|
|
78
88
|
return path.join(root, safeRelative)
|
|
79
89
|
}
|
|
80
90
|
|
|
91
|
+
/**
|
|
92
|
+
* @deprecated Use `StorageDriverFactory.resolveForAttachment()` + `driver.delete()` instead.
|
|
93
|
+
*/
|
|
81
94
|
export async function deletePartitionFile(
|
|
82
95
|
partitionCode: string,
|
|
83
96
|
storagePath: string,
|
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
import { promises as fs } from 'fs'
|
|
2
2
|
import path from 'path'
|
|
3
|
-
import { resolvePartitionRoot } from './storage'
|
|
4
|
-
|
|
5
|
-
const CACHE_ROOT_SEGMENTS = ['.cache', 'thumbnails']
|
|
6
3
|
|
|
7
4
|
function sanitizeSegment(value: string): string {
|
|
8
5
|
return value.replace(/[^a-zA-Z0-9._-]/g, '_')
|
|
9
6
|
}
|
|
10
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Resolves the thumbnail cache root. Uses a dedicated directory under
|
|
10
|
+
* `storage/.cache/thumbnails/<partitionCode>/` that is independent of
|
|
11
|
+
* the partition's storage backend (local or S3).
|
|
12
|
+
*/
|
|
13
|
+
function resolveCacheRoot(partitionCode: string): string {
|
|
14
|
+
return path.join(process.cwd(), 'storage', '.cache', 'thumbnails', sanitizeSegment(partitionCode))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function resolveCachePath(partitionCode: string, attachmentId: string, cacheKey: string): string {
|
|
18
|
+
return path.join(
|
|
19
|
+
resolveCacheRoot(partitionCode),
|
|
20
|
+
sanitizeSegment(attachmentId),
|
|
21
|
+
sanitizeSegment(cacheKey),
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
11
25
|
export function buildThumbnailCacheKey(
|
|
12
26
|
width?: number,
|
|
13
27
|
height?: number,
|
|
14
|
-
cropType?: 'cover' | 'contain'
|
|
28
|
+
cropType?: 'cover' | 'contain',
|
|
15
29
|
): string | null {
|
|
16
30
|
if (!width && !height) return null
|
|
17
31
|
const safeWidth = typeof width === 'number' && Number.isFinite(width) ? width : 'auto'
|
|
@@ -20,20 +34,10 @@ export function buildThumbnailCacheKey(
|
|
|
20
34
|
return `w${safeWidth}-h${safeHeight}-c${safeCrop}`
|
|
21
35
|
}
|
|
22
36
|
|
|
23
|
-
function resolveCachePath(partitionCode: string, attachmentId: string, cacheKey: string): string {
|
|
24
|
-
const root = resolvePartitionRoot(partitionCode)
|
|
25
|
-
return path.join(
|
|
26
|
-
root,
|
|
27
|
-
...CACHE_ROOT_SEGMENTS,
|
|
28
|
-
sanitizeSegment(attachmentId),
|
|
29
|
-
sanitizeSegment(cacheKey)
|
|
30
|
-
)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
37
|
export async function readThumbnailCache(
|
|
34
38
|
partitionCode: string,
|
|
35
39
|
attachmentId: string,
|
|
36
|
-
cacheKey: string
|
|
40
|
+
cacheKey: string,
|
|
37
41
|
): Promise<Buffer | null> {
|
|
38
42
|
const cachePath = resolveCachePath(partitionCode, attachmentId, cacheKey)
|
|
39
43
|
try {
|
|
@@ -47,16 +51,18 @@ export async function writeThumbnailCache(
|
|
|
47
51
|
partitionCode: string,
|
|
48
52
|
attachmentId: string,
|
|
49
53
|
cacheKey: string,
|
|
50
|
-
data: Buffer
|
|
54
|
+
data: Buffer,
|
|
51
55
|
): Promise<void> {
|
|
52
56
|
const cachePath = resolveCachePath(partitionCode, attachmentId, cacheKey)
|
|
53
57
|
await fs.mkdir(path.dirname(cachePath), { recursive: true })
|
|
54
58
|
await fs.writeFile(cachePath, data)
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
export async function clearAttachmentThumbnailCache(
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
export async function clearAttachmentThumbnailCache(
|
|
62
|
+
partitionCode: string,
|
|
63
|
+
attachmentId: string,
|
|
64
|
+
): Promise<void> {
|
|
65
|
+
const dir = path.join(resolveCacheRoot(partitionCode), sanitizeSegment(attachmentId))
|
|
60
66
|
try {
|
|
61
67
|
await fs.rm(dir, { recursive: true, force: true })
|
|
62
68
|
} catch {
|
|
@@ -295,6 +295,7 @@ function buildCredentialFields(credFields: CredentialField[]): CrudField[] {
|
|
|
295
295
|
) : field.helpText,
|
|
296
296
|
placeholder: field.placeholder,
|
|
297
297
|
required: field.required,
|
|
298
|
+
visibleWhen: field.visibleWhen,
|
|
298
299
|
}
|
|
299
300
|
|
|
300
301
|
if (field.type === 'secret') {
|
|
@@ -478,7 +479,15 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
|
|
|
478
479
|
{ fallback: null },
|
|
479
480
|
)
|
|
480
481
|
if (call.ok && call.result?.credentials) {
|
|
481
|
-
|
|
482
|
+
const next = { ...call.result.credentials }
|
|
483
|
+
if (currentIntegrationId === 'storage_s3') {
|
|
484
|
+
const authMode = next.authMode
|
|
485
|
+
if (authMode !== 'access_keys' && authMode !== 'ambient') {
|
|
486
|
+
const hasKeys = Boolean(next.accessKeyId || next.secretAccessKey)
|
|
487
|
+
next.authMode = hasKeys ? 'access_keys' : 'ambient'
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
setCredValues(next)
|
|
482
491
|
setCredentialsFormKey((current) => current + 1)
|
|
483
492
|
}
|
|
484
493
|
}, [resolveCurrentIntegrationId])
|
|
@@ -680,19 +689,32 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
|
|
|
680
689
|
if (!currentIntegrationId) return
|
|
681
690
|
setIsSavingCredentials(true)
|
|
682
691
|
try {
|
|
692
|
+
const sanitizedValues = { ...values }
|
|
693
|
+
if (currentIntegrationId === 'storage_s3') {
|
|
694
|
+
const authMode = sanitizedValues.authMode
|
|
695
|
+
if (authMode !== 'access_keys' && authMode !== 'ambient') {
|
|
696
|
+
const hasKeys = Boolean(sanitizedValues.accessKeyId || sanitizedValues.secretAccessKey)
|
|
697
|
+
sanitizedValues.authMode = hasKeys ? 'access_keys' : 'ambient'
|
|
698
|
+
}
|
|
699
|
+
if (sanitizedValues.authMode === 'ambient') {
|
|
700
|
+
delete sanitizedValues.accessKeyId
|
|
701
|
+
delete sanitizedValues.secretAccessKey
|
|
702
|
+
delete sanitizedValues.sessionToken
|
|
703
|
+
}
|
|
704
|
+
}
|
|
683
705
|
const call = await runMutationWithContext({
|
|
684
706
|
actionId: 'save-credentials',
|
|
685
707
|
tabId: 'credentials',
|
|
686
|
-
mutationPayload: { integrationId: currentIntegrationId, credentials:
|
|
708
|
+
mutationPayload: { integrationId: currentIntegrationId, credentials: sanitizedValues },
|
|
687
709
|
operation: () => apiCall(`/api/integrations/${encodeURIComponent(currentIntegrationId)}/credentials`, {
|
|
688
710
|
method: 'PUT',
|
|
689
711
|
headers: { 'Content-Type': 'application/json' },
|
|
690
|
-
body: JSON.stringify({ credentials:
|
|
712
|
+
body: JSON.stringify({ credentials: sanitizedValues }),
|
|
691
713
|
}, { fallback: null }),
|
|
692
714
|
})
|
|
693
715
|
|
|
694
716
|
if (call.ok) {
|
|
695
|
-
setCredValues(
|
|
717
|
+
setCredValues(sanitizedValues)
|
|
696
718
|
setCredentialsFormKey((current) => current + 1)
|
|
697
719
|
flash(t('integrations.detail.credentials.saved'), 'success')
|
|
698
720
|
return
|
|
@@ -792,6 +814,10 @@ export default function IntegrationDetailPage({ params }: IntegrationDetailPageP
|
|
|
792
814
|
const values = rawValues as Record<string, unknown>
|
|
793
815
|
|
|
794
816
|
editableCredentialFields.forEach((field) => {
|
|
817
|
+
if (field.visibleWhen) {
|
|
818
|
+
const targetValue = values[field.visibleWhen.field]
|
|
819
|
+
if (targetValue !== field.visibleWhen.equals) return
|
|
820
|
+
}
|
|
795
821
|
const value = values[field.key]
|
|
796
822
|
|
|
797
823
|
if (field.type === 'boolean') {
|
|
@@ -122,7 +122,15 @@ export default function BundleConfigPage({ params }: BundleConfigPageProps) {
|
|
|
122
122
|
{ fallback: null },
|
|
123
123
|
)
|
|
124
124
|
if (credCall.ok && credCall.result?.credentials) {
|
|
125
|
-
|
|
125
|
+
const next = { ...credCall.result.credentials }
|
|
126
|
+
if (currentBundleId === 'storage_s3') {
|
|
127
|
+
const authMode = next.authMode
|
|
128
|
+
if (authMode !== 'access_keys' && authMode !== 'ambient') {
|
|
129
|
+
const hasKeys = Boolean(next.accessKeyId || next.secretAccessKey)
|
|
130
|
+
next.authMode = hasKeys ? 'access_keys' : 'ambient'
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
setCredValues(next)
|
|
126
134
|
}
|
|
127
135
|
setIsLoading(false)
|
|
128
136
|
}, [resolveCurrentBundleId, t])
|
|
@@ -180,6 +188,11 @@ export default function BundleConfigPage({ params }: BundleConfigPageProps) {
|
|
|
180
188
|
|
|
181
189
|
const credFields = (detail.bundle.credentials?.fields ?? []).filter(isEditableCredentialField)
|
|
182
190
|
|
|
191
|
+
function isFieldVisible(field: CredentialField): boolean {
|
|
192
|
+
if (!field.visibleWhen) return true
|
|
193
|
+
return credValues[field.visibleWhen.field] === field.visibleWhen.equals
|
|
194
|
+
}
|
|
195
|
+
|
|
183
196
|
return (
|
|
184
197
|
<Page>
|
|
185
198
|
<PageBody className="space-y-6">
|
|
@@ -202,7 +215,7 @@ export default function BundleConfigPage({ params }: BundleConfigPageProps) {
|
|
|
202
215
|
<CardTitle>{t('integrations.bundle.sharedCredentials')}</CardTitle>
|
|
203
216
|
</CardHeader>
|
|
204
217
|
<CardContent className="space-y-4">
|
|
205
|
-
{credFields.map((field) => (
|
|
218
|
+
{credFields.filter(isFieldVisible).map((field) => (
|
|
206
219
|
<div key={field.key} className="space-y-1.5">
|
|
207
220
|
<label className="text-sm font-medium">
|
|
208
221
|
{field.label}{field.required && <span className="text-red-500 ml-0.5">*</span>}
|