@open-mercato/core 0.6.8-develop.7015.1.af90a2ddc7 → 0.6.8-develop.7019.1.f4c01c4b5c

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 (82) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/api_keys/api/keys/route.js +1 -1
  3. package/dist/modules/api_keys/api/keys/route.js.map +2 -2
  4. package/dist/modules/api_keys/di.js +9 -0
  5. package/dist/modules/api_keys/di.js.map +7 -0
  6. package/dist/modules/api_keys/services/principalService.js +31 -0
  7. package/dist/modules/api_keys/services/principalService.js.map +7 -0
  8. package/dist/modules/attachments/di.js +11 -1
  9. package/dist/modules/attachments/di.js.map +2 -2
  10. package/dist/modules/attachments/index.js.map +1 -1
  11. package/dist/modules/attachments/lib/attachment-service.js +313 -0
  12. package/dist/modules/attachments/lib/attachment-service.js.map +7 -0
  13. package/dist/modules/attachments/lib/scoped-upload-service.js +15 -2
  14. package/dist/modules/attachments/lib/scoped-upload-service.js.map +2 -2
  15. package/dist/modules/attachments/lib/upload-limits.js +6 -2
  16. package/dist/modules/attachments/lib/upload-limits.js.map +2 -2
  17. package/dist/modules/auth/data/entities.js.map +2 -2
  18. package/dist/modules/auth/di.js +35 -9
  19. package/dist/modules/auth/di.js.map +2 -2
  20. package/dist/modules/auth/services/principalService.js +200 -0
  21. package/dist/modules/auth/services/principalService.js.map +7 -0
  22. package/dist/modules/auth/services/rbacService.js +67 -24
  23. package/dist/modules/auth/services/rbacService.js.map +3 -3
  24. package/dist/modules/auth/services/roleOrganizationScope.js +41 -0
  25. package/dist/modules/auth/services/roleOrganizationScope.js.map +7 -0
  26. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +1 -1
  27. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
  28. package/dist/modules/customers/components/calendar/EventPeekPopover.js +3 -2
  29. package/dist/modules/customers/components/calendar/EventPeekPopover.js.map +2 -2
  30. package/dist/modules/data_sync/lib/abandoned-run.js +19 -0
  31. package/dist/modules/data_sync/lib/abandoned-run.js.map +7 -0
  32. package/dist/modules/data_sync/workers/sync-export.js +5 -1
  33. package/dist/modules/data_sync/workers/sync-export.js.map +2 -2
  34. package/dist/modules/data_sync/workers/sync-import.js +5 -1
  35. package/dist/modules/data_sync/workers/sync-import.js.map +2 -2
  36. package/dist/modules/directory/di.js +9 -0
  37. package/dist/modules/directory/di.js.map +2 -2
  38. package/dist/modules/directory/services/organizationHierarchyService.js +29 -0
  39. package/dist/modules/directory/services/organizationHierarchyService.js.map +7 -0
  40. package/dist/modules/directory/services/organizationScopeService.js +56 -0
  41. package/dist/modules/directory/services/organizationScopeService.js.map +7 -0
  42. package/dist/modules/directory/utils/organizationScope.js.map +2 -2
  43. package/dist/modules/warranty_claims/api/portal/attachments/route.js +2 -2
  44. package/dist/modules/warranty_claims/api/portal/attachments/route.js.map +2 -2
  45. package/dist/modules/workflows/lib/activity-executor.js +4 -0
  46. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  47. package/package.json +7 -7
  48. package/src/modules/api_keys/api/keys/route.ts +3 -1
  49. package/src/modules/api_keys/di.ts +7 -0
  50. package/src/modules/api_keys/services/principalService.ts +33 -0
  51. package/src/modules/attachments/AGENTS.md +15 -0
  52. package/src/modules/attachments/di.ts +16 -0
  53. package/src/modules/attachments/i18n/de.json +5 -0
  54. package/src/modules/attachments/i18n/en.json +5 -0
  55. package/src/modules/attachments/i18n/es.json +5 -0
  56. package/src/modules/attachments/i18n/ko.json +5 -0
  57. package/src/modules/attachments/i18n/pl.json +5 -0
  58. package/src/modules/attachments/index.ts +10 -0
  59. package/src/modules/attachments/lib/attachment-service.ts +468 -0
  60. package/src/modules/attachments/lib/scoped-upload-service.ts +46 -0
  61. package/src/modules/attachments/lib/upload-limits.ts +6 -2
  62. package/src/modules/auth/data/entities.ts +4 -1
  63. package/src/modules/auth/di.ts +46 -16
  64. package/src/modules/auth/services/principalService.ts +266 -0
  65. package/src/modules/auth/services/rbacService.ts +110 -24
  66. package/src/modules/auth/services/roleOrganizationScope.ts +70 -0
  67. package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +1 -1
  68. package/src/modules/customers/components/calendar/EventPeekPopover.tsx +4 -2
  69. package/src/modules/customers/i18n/de.json +1 -1
  70. package/src/modules/customers/i18n/en.json +1 -1
  71. package/src/modules/customers/i18n/es.json +1 -1
  72. package/src/modules/customers/i18n/ko.json +1 -1
  73. package/src/modules/customers/i18n/pl.json +1 -1
  74. package/src/modules/data_sync/lib/abandoned-run.ts +56 -0
  75. package/src/modules/data_sync/workers/sync-export.ts +4 -0
  76. package/src/modules/data_sync/workers/sync-import.ts +4 -0
  77. package/src/modules/directory/di.ts +14 -2
  78. package/src/modules/directory/services/organizationHierarchyService.ts +34 -0
  79. package/src/modules/directory/services/organizationScopeService.ts +85 -0
  80. package/src/modules/directory/utils/organizationScope.ts +4 -15
  81. package/src/modules/warranty_claims/api/portal/attachments/route.ts +2 -2
  82. package/src/modules/workflows/lib/activity-executor.ts +9 -0
@@ -0,0 +1,7 @@
1
+ import { asClass } from 'awilix'
2
+ import type { AppContainer } from '@open-mercato/shared/lib/di/container'
3
+ import { DefaultApiKeyPrincipalService } from './services/principalService'
4
+
5
+ export function register(container: AppContainer) {
6
+ container.register({ apiKeyPrincipalService: asClass(DefaultApiKeyPrincipalService).scoped() })
7
+ }
@@ -0,0 +1,33 @@
1
+ import type { FilterQuery } from '@mikro-orm/core'
2
+ import type { EntityManager } from '@mikro-orm/postgresql'
3
+ import type { ApiKeyPrincipalService, PrincipalScope } from '@open-mercato/shared/lib/auth/principal-service'
4
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
5
+ import { ApiKey } from '../data/entities'
6
+
7
+ function normalizeIds(ids: unknown): string[] {
8
+ if (!Array.isArray(ids)) return []
9
+ return Array.from(new Set(ids
10
+ .map((id) => typeof id === 'string' ? id.trim() : '')
11
+ .filter(Boolean)))
12
+ }
13
+
14
+ export class DefaultApiKeyPrincipalService implements ApiKeyPrincipalService {
15
+ constructor(private readonly em: EntityManager) {}
16
+
17
+ async resolveAssignedRoleIds(apiKeyId: string, scope: PrincipalScope): Promise<string[]> {
18
+ const apiKey = await findOneWithDecryption(
19
+ this.em,
20
+ ApiKey,
21
+ {
22
+ id: apiKeyId,
23
+ tenantId: scope.tenantId,
24
+ deletedAt: null,
25
+ $or: [{ organizationId: null }, { organizationId: scope.organizationId }],
26
+ } as FilterQuery<ApiKey>,
27
+ { fields: ['rolesJson', 'expiresAt'] as const },
28
+ scope,
29
+ )
30
+ if (!apiKey || (apiKey.expiresAt && apiKey.expiresAt.getTime() <= Date.now())) return []
31
+ return normalizeIds(apiKey.rolesJson)
32
+ }
33
+ }
@@ -34,6 +34,18 @@ write time.
34
34
 
35
35
  ## Always
36
36
 
37
+ - Cross-module consumers MUST resolve the public `attachmentService` DI contract
38
+ and import its type from `@open-mercato/core/modules/attachments`. Keep
39
+ attachment entities, partitions, storage drivers, quota accounting, file
40
+ security, and `checkAttachmentAccess` behind that boundary.
41
+ - Cross-module multipart uploads MUST use `attachmentService.readUploadForm()` so
42
+ the raw request stream is bounded before `FormData` decoding, including when
43
+ `Content-Length` is missing or the request is chunked.
44
+ - Cross-module permanent deletion MUST use `attachmentService.releaseScoped()`
45
+ with the exact owner, assignment, partition, and tenant/organization scope. In
46
+ an ambient transaction pass `{ flush: false }`, commit the returned database
47
+ removal first, and invoke the returned provider cleanup only after commit;
48
+ never delete provider bytes before a transaction can still roll back.
37
49
  - **MUST call `assertAttachmentScopeInvariant({ tenantId, organizationId })` from
38
50
  `lib/access.ts` before persisting any new `Attachment` row.** It throws on a
39
51
  partial-null scope and accepts both fully-scoped and fully-global rows. The
@@ -46,6 +58,9 @@ write time.
46
58
 
47
59
  ## Never
48
60
 
61
+ - Never make a peer module construct `StorageDriverFactory` or read Attachment /
62
+ AttachmentPartition entities directly as a fallback. Missing service wiring
63
+ must fail closed.
49
64
  - **Never create a partial-null attachment** (one scope column set, the other null).
50
65
  - **Never read or expose attachment rows without `checkAttachmentAccess`** — bypassing
51
66
  it reintroduces the cross-tenant fail-open class.
@@ -2,6 +2,7 @@ import { asFunction, asValue } from 'awilix'
2
2
  import type { AppContainer } from '@open-mercato/shared/lib/di/container'
3
3
  import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
4
4
  import { StorageDriverFactory } from './lib/drivers/driverFactory'
5
+ import { DefaultAttachmentService } from './lib/attachment-service'
5
6
  import { createAttachmentQuotaService, type AttachmentQuotaService } from './lib/quota-service'
6
7
  import { scheduleAttachmentQuotaRecovery } from './lib/quota-recovery-queue'
7
8
  import { AttachmentTargetAccessService } from './lib/target-access-service'
@@ -46,5 +47,20 @@ export function register(container: AppContainer) {
46
47
  )
47
48
  .singleton()
48
49
  .proxy(),
50
+ // The scoped upload service is handed over as a lazy resolver so module
51
+ // uploads share the public attachment route's single fenced reservation
52
+ // ledger, without dragging that service's own dependencies into every
53
+ // container that merely constructs an `attachmentService`.
54
+ attachmentService: asFunction((cradle: {
55
+ em: ConstructorParameters<typeof DefaultAttachmentService>[0]
56
+ storageDriverFactory: ConstructorParameters<typeof DefaultAttachmentService>[1]
57
+ attachmentScopedUploadService: ScopedAttachmentUploadService
58
+ }) => new DefaultAttachmentService(
59
+ cradle.em,
60
+ cradle.storageDriverFactory,
61
+ () => cradle.attachmentScopedUploadService ?? null,
62
+ ))
63
+ .scoped()
64
+ .proxy(),
49
65
  })
50
66
  }
@@ -2,12 +2,17 @@
2
2
  "attachments.errors.activeContentBlocked": "Das Hochladen aktiver Inhalte ist nicht erlaubt.",
3
3
  "attachments.errors.dangerousExecutable": "Ausführbare Dateitypen sind als Anhänge nicht erlaubt.",
4
4
  "attachments.errors.maxUploadSize": "Der Anhang überschreitet die maximal zulässige Upload-Größe.",
5
+ "attachments.errors.partitionUnavailable": "Die Anhang-Partition ist für diesen Bereich nicht verfügbar.",
6
+ "attachments.errors.persistenceFailed": "Der Anhang konnte nicht gespeichert werden.",
5
7
  "attachments.errors.publicPartitionBlocked": "Öffentliche Speicherpartitionen können für diesen Upload nicht explizit ausgewählt werden.",
6
8
  "attachments.errors.quotaExceeded": "Das Speicherlimit für Anhänge dieses Tenants wurde überschritten.",
7
9
  "attachments.errors.quotaRecoveryUnsupported": "Der Speichertreiber kann nicht an der Kontingent-Wiederherstellung teilnehmen.",
8
10
  "attachments.errors.quotaUnavailable": "Die Speicherkontingent-Abrechnung ist nicht verfügbar.",
9
11
  "attachments.errors.selectOrganization": "Wählen Sie eine bestimmte Organisation aus, bevor Sie einen Anhang hochladen.",
12
+ "attachments.errors.storageFailed": "Die Anhangsdatei konnte nicht im Speicher abgelegt werden.",
10
13
  "attachments.errors.storagePathExists": "Der Zielspeicherpfad existiert bereits.",
14
+ "attachments.errors.uploadFailed": "Das Hochladen des Anhangs ist fehlgeschlagen.",
15
+ "attachments.errors.uploadServiceUnavailable": "Der Anhang-Upload-Dienst ist nicht verfügbar.",
11
16
  "attachments.library.actions.chooseReplacement": "Ersatzdatei auswählen",
12
17
  "attachments.library.actions.copied": "Link kopiert.",
13
18
  "attachments.library.actions.copyError": "Link konnte nicht kopiert werden.",
@@ -2,12 +2,17 @@
2
2
  "attachments.errors.activeContentBlocked": "Active content uploads are not allowed.",
3
3
  "attachments.errors.dangerousExecutable": "Executable file types are not allowed as attachments.",
4
4
  "attachments.errors.maxUploadSize": "Attachment exceeds the maximum upload size.",
5
+ "attachments.errors.partitionUnavailable": "Attachment partition is not available for this scope.",
6
+ "attachments.errors.persistenceFailed": "Failed to persist attachment.",
5
7
  "attachments.errors.publicPartitionBlocked": "Public storage partitions cannot be selected explicitly for this upload.",
6
8
  "attachments.errors.quotaExceeded": "Attachment storage quota exceeded for this tenant.",
7
9
  "attachments.errors.quotaRecoveryUnsupported": "Storage driver cannot participate in quota recovery.",
8
10
  "attachments.errors.quotaUnavailable": "Storage quota accounting is unavailable.",
9
11
  "attachments.errors.selectOrganization": "Select a specific organization before uploading an attachment.",
12
+ "attachments.errors.storageFailed": "Failed to store the attachment file.",
10
13
  "attachments.errors.storagePathExists": "The target storage path already exists.",
14
+ "attachments.errors.uploadFailed": "Attachment upload failed.",
15
+ "attachments.errors.uploadServiceUnavailable": "The attachment upload service is not available.",
11
16
  "attachments.library.actions.chooseReplacement": "Choose replacement file",
12
17
  "attachments.library.actions.copied": "Link copied.",
13
18
  "attachments.library.actions.copyError": "Unable to copy link.",
@@ -2,12 +2,17 @@
2
2
  "attachments.errors.activeContentBlocked": "No se permite la carga de contenido activo.",
3
3
  "attachments.errors.dangerousExecutable": "No se permiten archivos ejecutables como adjuntos.",
4
4
  "attachments.errors.maxUploadSize": "El archivo adjunto supera el tamaño máximo de carga permitido.",
5
+ "attachments.errors.partitionUnavailable": "La partición de adjuntos no está disponible para este ámbito.",
6
+ "attachments.errors.persistenceFailed": "No se pudo guardar el archivo adjunto.",
5
7
  "attachments.errors.publicPartitionBlocked": "No se pueden seleccionar explícitamente particiones de almacenamiento públicas para esta carga.",
6
8
  "attachments.errors.quotaExceeded": "Se superó la cuota de almacenamiento de adjuntos para este tenant.",
7
9
  "attachments.errors.quotaRecoveryUnsupported": "El controlador de almacenamiento no puede participar en la recuperación de cuota.",
8
10
  "attachments.errors.quotaUnavailable": "La contabilidad de la cuota de almacenamiento no está disponible.",
9
11
  "attachments.errors.selectOrganization": "Selecciona una organización específica antes de subir un adjunto.",
12
+ "attachments.errors.storageFailed": "No se pudo almacenar el archivo adjunto.",
10
13
  "attachments.errors.storagePathExists": "La ruta de almacenamiento de destino ya existe.",
14
+ "attachments.errors.uploadFailed": "Error al subir el archivo adjunto.",
15
+ "attachments.errors.uploadServiceUnavailable": "El servicio de carga de adjuntos no está disponible.",
11
16
  "attachments.library.actions.chooseReplacement": "Elegir archivo de reemplazo",
12
17
  "attachments.library.actions.copied": "Enlace copiado.",
13
18
  "attachments.library.actions.copyError": "No se pudo copiar el enlace.",
@@ -2,12 +2,17 @@
2
2
  "attachments.errors.activeContentBlocked": "활성 콘텐츠 업로드는 허용되지 않습니다.",
3
3
  "attachments.errors.dangerousExecutable": "실행 파일 형식은 첨부 파일로 허용되지 않습니다.",
4
4
  "attachments.errors.maxUploadSize": "첨부 파일이 최대 업로드 크기를 초과합니다.",
5
+ "attachments.errors.partitionUnavailable": "이 범위에서는 첨부 파일 파티션을 사용할 수 없습니다.",
6
+ "attachments.errors.persistenceFailed": "첨부 파일을 저장하지 못했습니다.",
5
7
  "attachments.errors.publicPartitionBlocked": "이 업로드에는 공개 저장소 파티션을 명시적으로 선택할 수 없습니다.",
6
8
  "attachments.errors.quotaExceeded": "이 테넌트의 첨부 파일 저장소 할당량을 초과했습니다.",
7
9
  "attachments.errors.quotaRecoveryUnsupported": "저장 용량 복구가 지원되지 않습니다.",
8
10
  "attachments.errors.quotaUnavailable": "첨부파일 저장 용량 정보를 확인할 수 없습니다.",
9
11
  "attachments.errors.selectOrganization": "첨부파일을 업로드하기 전에 특정 조직을 선택하세요.",
12
+ "attachments.errors.storageFailed": "첨부 파일을 저장소에 기록하지 못했습니다.",
10
13
  "attachments.errors.storagePathExists": "대상 저장 경로가 이미 존재합니다.",
14
+ "attachments.errors.uploadFailed": "첨부 파일 업로드에 실패했습니다.",
15
+ "attachments.errors.uploadServiceUnavailable": "첨부 파일 업로드 서비스를 사용할 수 없습니다.",
11
16
  "attachments.library.actions.chooseReplacement": "교체 파일 선택",
12
17
  "attachments.library.actions.copied": "링크가 복사되었습니다.",
13
18
  "attachments.library.actions.copyError": "링크를 복사할 수 없습니다.",
@@ -2,12 +2,17 @@
2
2
  "attachments.errors.activeContentBlocked": "Przesyłanie treści aktywnych nie jest dozwolone.",
3
3
  "attachments.errors.dangerousExecutable": "Pliki wykonywalne nie są dozwolone jako załączniki.",
4
4
  "attachments.errors.maxUploadSize": "Załącznik przekracza maksymalny dopuszczalny rozmiar przesyłania.",
5
+ "attachments.errors.partitionUnavailable": "Partycja załączników nie jest dostępna w tym zakresie.",
6
+ "attachments.errors.persistenceFailed": "Nie udało się zapisać załącznika.",
5
7
  "attachments.errors.publicPartitionBlocked": "Nie można jawnie wybrać publicznej partycji do tego przesyłania.",
6
8
  "attachments.errors.quotaExceeded": "Przekroczono limit miejsca na załączniki dla tego tenantu.",
7
9
  "attachments.errors.quotaRecoveryUnsupported": "Sterownik przechowywania nie może uczestniczyć w odzyskiwaniu limitu.",
8
10
  "attachments.errors.quotaUnavailable": "Rozliczanie limitu przestrzeni dyskowej jest niedostępne.",
9
11
  "attachments.errors.selectOrganization": "Wybierz konkretną organizację przed przesłaniem załącznika.",
12
+ "attachments.errors.storageFailed": "Nie udało się zapisać pliku załącznika w magazynie.",
10
13
  "attachments.errors.storagePathExists": "Docelowa ścieżka przechowywania już istnieje.",
14
+ "attachments.errors.uploadFailed": "Przesyłanie załącznika nie powiodło się.",
15
+ "attachments.errors.uploadServiceUnavailable": "Usługa przesyłania załączników jest niedostępna.",
11
16
  "attachments.library.actions.chooseReplacement": "Wybierz plik zastępczy",
12
17
  "attachments.library.actions.copied": "Link skopiowany.",
13
18
  "attachments.library.actions.copyError": "Nie można skopiować linku.",
@@ -9,3 +9,13 @@ export const metadata: ModuleInfo = {
9
9
  license: 'MIT',
10
10
  }
11
11
 
12
+ export {
13
+ type AttachmentOwner,
14
+ type AttachmentProviderCleanup,
15
+ type AttachmentService,
16
+ type CreatedScopedAttachment,
17
+ type CreateScopedAttachmentInput,
18
+ type ReadScopedAttachmentInput,
19
+ type ReadScopedAttachmentResult,
20
+ type ReleaseScopedAttachmentInput,
21
+ } from './lib/attachment-service'