@open-mercato/core 0.6.6-develop.6543.1.49fadb4c31 → 0.6.6
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/api_keys/api/keys/route.js +9 -23
- package/dist/modules/api_keys/api/keys/route.js.map +2 -2
- package/dist/modules/api_keys/cli.js +1 -2
- package/dist/modules/api_keys/cli.js.map +2 -2
- package/dist/modules/attachments/api/file/[id]/route.js +7 -9
- package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +7 -9
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
- package/dist/modules/attachments/api/route.js +17 -21
- package/dist/modules/attachments/api/route.js.map +2 -2
- package/dist/modules/auth/api/users/route.js +2 -6
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/cli.js +2 -3
- package/dist/modules/auth/cli.js.map +2 -2
- package/dist/modules/configs/lib/upgrade-actions.js +0 -22
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection-table.js +2 -0
- package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +1 -7
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js +7 -11
- package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
- package/dist/modules/dashboards/api/roles/widgets/route.js +3 -22
- package/dist/modules/dashboards/api/roles/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/api/users/widgets/route.js +3 -22
- package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
- package/dist/modules/dashboards/cli.js +3 -4
- package/dist/modules/dashboards/cli.js.map +2 -2
- package/dist/modules/dashboards/lib/widgetAssignmentScope.js +1 -9
- package/dist/modules/dashboards/lib/widgetAssignmentScope.js.map +2 -2
- package/dist/modules/dictionaries/commands/entries.js +14 -7
- package/dist/modules/dictionaries/commands/entries.js.map +2 -2
- package/dist/modules/dictionaries/commands/entry-operations.js +16 -7
- package/dist/modules/dictionaries/commands/entry-operations.js.map +2 -2
- package/dist/modules/dictionaries/commands/factory.js +3 -9
- package/dist/modules/dictionaries/commands/factory.js.map +2 -2
- package/dist/modules/dictionaries/fields/dictionary.js +41 -56
- package/dist/modules/dictionaries/fields/dictionary.js.map +2 -2
- package/dist/modules/directory/utils/organizationScope.js +3 -10
- package/dist/modules/directory/utils/organizationScope.js.map +2 -2
- package/dist/modules/entities/api/records.js +4 -5
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js +0 -1
- package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js +49 -49
- package/dist/modules/entities/backend/entities/user/[entityId]/page.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js +0 -1
- package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js.map +2 -2
- package/dist/modules/entities/cli.js +1 -2
- package/dist/modules/entities/cli.js.map +2 -2
- package/dist/modules/query_index/api/status.js +8 -2
- package/dist/modules/query_index/api/status.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +0 -44
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +27 -2
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/sales/components/documents/AddressesSection.js +2 -6
- package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +0 -1
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +0 -9
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentForm.js +0 -44
- package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentDialog.js +38 -59
- package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
- package/dist/modules/sales/lib/shipments/snapshots.js +1 -27
- package/dist/modules/sales/lib/shipments/snapshots.js.map +2 -2
- package/dist/modules/workflows/frontend/checkout-demo/page.js +8 -1
- package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
- package/dist/modules/workflows/lib/activity-executor.js +1 -1
- package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
- package/dist/modules/workflows/lib/find-definition.js +18 -2
- package/dist/modules/workflows/lib/find-definition.js.map +2 -2
- package/dist/modules/workflows/lib/signal-handler.js +3 -2
- package/dist/modules/workflows/lib/signal-handler.js.map +2 -2
- package/dist/modules/workflows/lib/step-handler.js +4 -8
- package/dist/modules/workflows/lib/step-handler.js.map +2 -2
- package/dist/modules/workflows/lib/task-handler.js +3 -5
- package/dist/modules/workflows/lib/task-handler.js.map +2 -2
- package/dist/modules/workflows/lib/timer-handler.js +2 -1
- package/dist/modules/workflows/lib/timer-handler.js.map +2 -2
- package/dist/modules/workflows/lib/transition-handler.js +5 -13
- package/dist/modules/workflows/lib/transition-handler.js.map +2 -2
- package/dist/modules/workflows/lib/workflow-executor.js +11 -9
- package/dist/modules/workflows/lib/workflow-executor.js.map +2 -2
- package/dist/modules/workflows/migrations/Migration20260715120000.js +68 -0
- package/dist/modules/workflows/migrations/Migration20260715120000.js.map +7 -0
- package/dist/modules/workflows/migrations/Migration20260716120000.js +24 -0
- package/dist/modules/workflows/migrations/Migration20260716120000.js.map +7 -0
- package/dist/modules/workflows/workflows.js +2 -0
- package/dist/modules/workflows/workflows.js.map +2 -2
- package/package.json +8 -9
- package/src/modules/api_keys/api/keys/route.ts +9 -27
- package/src/modules/api_keys/cli.ts +3 -2
- package/src/modules/attachments/api/file/[id]/route.ts +7 -11
- package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +7 -11
- package/src/modules/attachments/api/route.ts +21 -26
- package/src/modules/auth/api/users/route.ts +2 -11
- package/src/modules/auth/cli.ts +2 -3
- package/src/modules/configs/i18n/de.json +1 -5
- package/src/modules/configs/i18n/en.json +1 -5
- package/src/modules/configs/i18n/es.json +1 -5
- package/src/modules/configs/i18n/pl.json +1 -5
- package/src/modules/configs/lib/upgrade-actions.ts +0 -24
- package/src/modules/customer_accounts/AGENTS.md +6 -8
- package/src/modules/customer_accounts/widgets/injection-table.ts +2 -0
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +1 -11
- package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +7 -13
- package/src/modules/dashboards/api/roles/widgets/route.ts +3 -22
- package/src/modules/dashboards/api/users/widgets/route.ts +3 -22
- package/src/modules/dashboards/cli.ts +9 -4
- package/src/modules/dashboards/lib/widgetAssignmentScope.ts +0 -18
- package/src/modules/dictionaries/commands/entries.ts +16 -7
- package/src/modules/dictionaries/commands/entry-operations.ts +18 -8
- package/src/modules/dictionaries/commands/factory.ts +2 -8
- package/src/modules/dictionaries/fields/dictionary.tsx +32 -55
- package/src/modules/dictionaries/i18n/de.json +0 -2
- package/src/modules/dictionaries/i18n/en.json +0 -2
- package/src/modules/dictionaries/i18n/es.json +0 -2
- package/src/modules/dictionaries/i18n/pl.json +0 -2
- package/src/modules/directory/utils/organizationScope.ts +2 -36
- package/src/modules/entities/api/records.ts +7 -5
- package/src/modules/entities/backend/entities/system/[entityId]/page.meta.ts +0 -1
- package/src/modules/entities/backend/entities/user/[entityId]/page.meta.ts +0 -1
- package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +49 -51
- package/src/modules/entities/cli.ts +1 -2
- package/src/modules/entities/i18n/de.json +0 -34
- package/src/modules/entities/i18n/en.json +0 -34
- package/src/modules/entities/i18n/es.json +0 -34
- package/src/modules/entities/i18n/pl.json +0 -34
- package/src/modules/query_index/api/status.ts +8 -2
- package/src/modules/sales/commands/documents.ts +0 -65
- package/src/modules/sales/commands/returns.ts +32 -2
- package/src/modules/sales/components/documents/AddressesSection.tsx +2 -6
- package/src/modules/sales/components/documents/ItemsSection.tsx +0 -5
- package/src/modules/sales/components/documents/LineItemDialog.tsx +0 -10
- package/src/modules/sales/components/documents/SalesDocumentForm.tsx +0 -46
- package/src/modules/sales/components/documents/ShipmentDialog.tsx +40 -65
- package/src/modules/sales/i18n/de.json +0 -4
- package/src/modules/sales/i18n/en.json +0 -4
- package/src/modules/sales/i18n/es.json +0 -4
- package/src/modules/sales/i18n/pl.json +0 -4
- package/src/modules/sales/lib/shipments/snapshots.ts +0 -30
- package/src/modules/workflows/frontend/checkout-demo/page.tsx +12 -1
- package/src/modules/workflows/lib/activity-executor.ts +22 -2
- package/src/modules/workflows/lib/find-definition.ts +53 -1
- package/src/modules/workflows/lib/signal-handler.ts +5 -4
- package/src/modules/workflows/lib/step-handler.ts +4 -8
- package/src/modules/workflows/lib/task-handler.ts +2 -4
- package/src/modules/workflows/lib/timer-handler.ts +3 -2
- package/src/modules/workflows/lib/transition-handler.ts +5 -13
- package/src/modules/workflows/lib/workflow-executor.ts +11 -8
- package/src/modules/workflows/migrations/Migration20260715120000.ts +114 -0
- package/src/modules/workflows/migrations/Migration20260716120000.ts +57 -0
- package/src/modules/workflows/workflows.ts +2 -0
- package/dist/modules/attachments/lib/reconcileOrganization.js +0 -110
- package/dist/modules/attachments/lib/reconcileOrganization.js.map +0 -7
- package/dist/modules/attachments/lib/requestScope.js +0 -10
- package/dist/modules/attachments/lib/requestScope.js.map +0 -7
- package/dist/modules/customers/components/detail/useDealsAccess.js +0 -16
- package/dist/modules/customers/components/detail/useDealsAccess.js.map +0 -7
- package/src/modules/attachments/lib/reconcileOrganization.ts +0 -188
- package/src/modules/attachments/lib/requestScope.ts +0 -27
- package/src/modules/customers/components/detail/useDealsAccess.ts +0 -19
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/workflows/migrations/Migration20260716120000.ts"],
|
|
4
|
+
"sourcesContent": ["import { Migration } from '@mikro-orm/migrations'\n\nconst CODE_WORKFLOW_ID = 'workflows.checkout-demo'\n\n/**\n * Retires the persisted checkout-demo seed rows so runtime resolves the\n * maintained CODE definition instead (issue #4211).\n *\n * Background: `workflows.checkout-demo` is now code-defined\n * (`packages/core/src/modules/workflows/workflows.ts`), and only the code\n * definition forwards the cart's line items + shipping/tax adjustments to\n * `POST /api/sales/orders`. But existing tenants still carry a persisted\n * `workflow_definitions` row from the old seed \u2014 originally seeded under ids\n * like `checkout_simple_v1` / names like `Simple Checkout Flow` and later\n * `Checkout with Payment Webhook`, then renamed to `workflows.checkout-demo`\n * by Migration20260428102318. Its `create_order` body never sent `lines`, and\n * because `findWorkflowDefinition` resolves the DB row first, that stale row\n * SHADOWS the code definition \u2014 so orders come out with zero line items and\n * $0.00 totals. Patching the persisted body is unreliable: the seed's name and\n * body shape vary across historical versions, so a fingerprint on either misses\n * whole cohorts of tenants (the defect QA hit).\n *\n * Instead, soft-delete the seeded rows. This is body/name-agnostic, so it fixes\n * every historical seed variant:\n * - New instances: `findWorkflowDefinition` filters `deleted_at IS NULL`, so\n * the retired row is skipped and the code definition is used (correct body).\n * - In-flight instances: `findDefinitionForInstance` loads the definition by\n * `id` with no soft-delete filter (WorkflowDefinition has no global filter),\n * so instances that reference the row keep resolving and advancing.\n *\n * Scope is limited to `code_workflow_id IS NULL` rows \u2014 user customizations of\n * the code definition set `code_workflow_id` and MUST NOT be retired. The\n * `deleted_at IS NULL` guard makes the migration idempotent.\n */\nexport function buildRetireSeededCheckoutDemoSql(): string {\n return `\n update \"workflow_definitions\" as \"wf\"\n set \"deleted_at\" = current_timestamp,\n \"updated_at\" = current_timestamp\n where \"wf\".\"workflow_id\" = '${CODE_WORKFLOW_ID}'\n and \"wf\".\"code_workflow_id\" is null\n and \"wf\".\"deleted_at\" is null;\n `.trim()\n}\n\nexport class Migration20260716120000 extends Migration {\n override async up(): Promise<void> {\n this.addSql(buildRetireSeededCheckoutDemoSql())\n }\n\n override async down(): Promise<void> {\n // Forward-only. Restoring `deleted_at = null` would re-shadow the maintained\n // code definition with the stale seed body and re-introduce zero-line orders\n // (#4211). The code definition supersedes the seed, so there is nothing to\n // restore.\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAE1B,MAAM,mBAAmB;AAgClB,SAAS,mCAA2C;AACzD,SAAO;AAAA;AAAA;AAAA;AAAA,kCAIyB,gBAAgB;AAAA;AAAA;AAAA,IAG9C,KAAK;AACT;AAEO,MAAM,gCAAgC,UAAU;AAAA,EACrD,MAAe,KAAoB;AACjC,SAAK,OAAO,iCAAiC,CAAC;AAAA,EAChD;AAAA,EAEA,MAAe,OAAsB;AAAA,EAKrC;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -222,6 +222,8 @@ const checkoutDemo = defineWorkflow({
|
|
|
222
222
|
customerEntityId: "{{context.customer.id}}",
|
|
223
223
|
currencyCode: "{{context.cart.currency}}",
|
|
224
224
|
placedAt: "{{now}}",
|
|
225
|
+
lines: "{{context.cart.orderLines}}",
|
|
226
|
+
adjustments: "{{context.cart.orderAdjustments}}",
|
|
225
227
|
grandTotalGrossAmount: "{{context.cart.total}}"
|
|
226
228
|
},
|
|
227
229
|
validateTenantMatch: true
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/workflows/workflows.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Workflows Module \u2014 Code-Based Workflow Definitions\n *\n * Demo workflows shipped with the workflows module.\n * These are auto-discovered by the generator and loaded into the in-memory registry.\n */\n\nimport { defineWorkflow, createWorkflowsModuleConfig } from '@open-mercato/shared/modules/workflows'\n\n// ============================================================================\n// Simple Approval Workflow\n// ============================================================================\n\nconst simpleApproval = defineWorkflow({\n workflowId: 'workflows.simple-approval',\n workflowName: 'Simple Approval Workflow',\n description: 'Basic three-step approval workflow: Start \u2192 Approval \u2192 End',\n metadata: { category: 'Testing', tags: ['test', 'approval', 'simple'], icon: 'check-circle' },\n steps: [\n { stepId: 'start', stepName: 'Start', stepType: 'START', description: 'Initialize workflow' },\n {\n stepId: 'approval',\n stepName: 'Pending Approval',\n stepType: 'USER_TASK',\n description: 'Approve or reject request',\n userTaskConfig: { assignedTo: 'approver', slaDuration: 'PT1H' },\n },\n { stepId: 'end', stepName: 'Complete', stepType: 'END', description: 'Workflow complete' },\n ] as const,\n transitions: [\n {\n transitionId: 'start_to_approval',\n transitionName: 'Submit for Approval',\n fromStepId: 'start',\n toStepId: 'approval',\n trigger: 'auto',\n priority: 100,\n activities: [\n {\n activityId: 'notify_approver',\n activityName: 'Notify Approver',\n activityType: 'SEND_EMAIL',\n config: {\n to: '{{context.approver.email}}',\n subject: 'Approval Required: {{context.request.title}}',\n template: 'approval_request',\n data: {\n requestId: '{{context.request.id}}',\n title: '{{context.request.title}}',\n requester: '{{context.requester.name}}',\n },\n },\n timeout: 'PT10S',\n async: true,\n retryPolicy: { maxAttempts: 3, initialIntervalMs: 2000, backoffCoefficient: 2, maxIntervalMs: 20000 },\n },\n {\n activityId: 'emit_approval_requested',\n activityName: 'Emit Approval Requested Event',\n activityType: 'EMIT_EVENT',\n config: {\n eventType: 'approval.requested',\n payload: { requestId: '{{context.request.id}}', workflowInstanceId: '{{workflow.instanceId}}' },\n },\n async: true,\n },\n ],\n },\n {\n transitionId: 'approval_to_end',\n transitionName: 'Approval Decision',\n fromStepId: 'approval',\n toStepId: 'end',\n trigger: 'manual',\n priority: 100,\n activities: [\n {\n activityId: 'notify_requester',\n activityName: 'Notify Requester of Decision',\n activityType: 'SEND_EMAIL',\n config: {\n to: '{{context.requester.email}}',\n subject: 'Approval Decision: {{context.request.title}}',\n template: 'approval_decision',\n data: {\n requestId: '{{context.request.id}}',\n title: '{{context.request.title}}',\n decision: '{{task.result.approved}}',\n comments: '{{task.result.comments}}',\n approver: '{{task.completedBy}}',\n },\n },\n timeout: 'PT10S',\n async: true,\n },\n {\n activityId: 'emit_approval_completed',\n activityName: 'Emit Approval Completed Event',\n activityType: 'EMIT_EVENT',\n config: {\n eventType: 'approval.completed',\n payload: {\n requestId: '{{context.request.id}}',\n approved: '{{task.result.approved}}',\n approvedBy: '{{task.completedBy}}',\n timestamp: '{{now}}',\n },\n },\n async: true,\n },\n ],\n },\n ],\n})\n\n// ============================================================================\n// Checkout Demo Workflow\n// ============================================================================\n\nconst checkoutDemo = defineWorkflow({\n workflowId: 'workflows.checkout-demo',\n workflowName: 'Checkout with Payment Webhook',\n description: 'Realistic checkout workflow with signal-based payment confirmation',\n metadata: { category: 'E-commerce', tags: ['checkout', 'demo', 'payments', 'signals'], icon: 'ShoppingCart' },\n steps: [\n {\n stepId: 'start',\n stepName: 'Start',\n stepType: 'START',\n description: 'Checkout process initiated',\n preConditions: [{\n ruleId: 'workflow_checkout_cart_not_empty',\n required: true,\n validationMessage: {\n en: 'Your shopping cart is empty. Please add items before starting checkout.',\n pl: 'Tw\u00F3j koszyk jest pusty. Dodaj produkty przed rozpocz\u0119ciem zakupu.',\n },\n }],\n },\n { stepId: 'cart_validation', stepName: 'Cart Validation', stepType: 'AUTOMATED', description: 'Validate cart has items and inventory is available', timeout: 'PT10S' },\n {\n stepId: 'customer_info',\n stepName: 'Customer Information',\n stepType: 'USER_TASK',\n description: 'Collect customer shipping and contact information',\n userTaskConfig: {\n formSchema: {\n type: 'object',\n properties: {\n fullName: { type: 'string', title: 'Full Name', description: \"Customer's full legal name\" },\n email: { type: 'string', format: 'email', title: 'Email Address', description: 'Email for order confirmation' },\n phone: { type: 'string', title: 'Phone Number', description: 'Contact number for delivery' },\n comment: { type: 'string', title: 'Order Comment', maxLength: 500, description: 'Special instructions or notes' },\n },\n required: ['fullName', 'email'],\n },\n slaDuration: 'PT24H',\n },\n },\n { stepId: 'payment_initiation', stepName: 'Initiate Payment', stepType: 'AUTOMATED', description: 'Send payment request to payment provider', timeout: 'PT10S' },\n {\n stepId: 'wait_payment_confirmation',\n stepName: 'Wait for Payment Confirmation',\n stepType: 'WAIT_FOR_SIGNAL',\n description: 'Waiting for payment provider webhook confirmation',\n signalConfig: { signalName: 'payment_confirmed', timeout: 'PT5M' },\n },\n { stepId: 'order_confirmation', stepName: 'Order Confirmation', stepType: 'AUTOMATED', description: 'Create order record and send confirmation', timeout: 'PT15S' },\n { stepId: 'end', stepName: 'Complete', stepType: 'END', description: 'Checkout completed successfully' },\n ] as const,\n transitions: [\n {\n transitionId: 'start_to_cart',\n transitionName: 'Initialize Checkout',\n fromStepId: 'start',\n toStepId: 'cart_validation',\n trigger: 'auto',\n priority: 100,\n activities: [{\n activityId: 'log_checkout_start',\n activityName: 'Log Checkout Start',\n activityType: 'EMIT_EVENT',\n config: {\n eventName: 'checkout.started',\n payload: { customerId: '{{context.customerId}}', cartId: '{{context.cartId}}', currency: '{{context.currency}}', timestamp: '{{now}}' },\n },\n async: false,\n }],\n },\n {\n transitionId: 'cart_to_customer_info',\n transitionName: 'Collect Customer Information',\n fromStepId: 'cart_validation',\n toStepId: 'customer_info',\n trigger: 'auto',\n priority: 100,\n preConditions: [{ ruleId: 'workflow_checkout_cart_not_empty', required: true }],\n },\n {\n transitionId: 'customer_info_to_payment',\n transitionName: 'Initiate Payment',\n fromStepId: 'customer_info',\n toStepId: 'payment_initiation',\n trigger: 'auto',\n priority: 100,\n },\n {\n transitionId: 'payment_to_wait_confirmation',\n transitionName: 'Wait for Confirmation',\n fromStepId: 'payment_initiation',\n toStepId: 'wait_payment_confirmation',\n trigger: 'auto',\n priority: 100,\n },\n {\n transitionId: 'confirmation_to_order',\n transitionName: 'Payment Confirmed',\n fromStepId: 'wait_payment_confirmation',\n toStepId: 'order_confirmation',\n trigger: 'auto',\n priority: 100,\n },\n {\n transitionId: 'confirmation_to_end',\n transitionName: 'Finalize Order',\n fromStepId: 'order_confirmation',\n toStepId: 'end',\n trigger: 'auto',\n priority: 100,\n activities: [\n {\n activityId: 'create_order',\n activityName: 'Create Order Record',\n activityType: 'CALL_API',\n config: {\n endpoint: '/api/sales/orders',\n method: 'POST',\n body: {\n customerEntityId: '{{context.customer.id}}',\n currencyCode: '{{context.cart.currency}}',\n placedAt: '{{now}}',\n grandTotalGrossAmount: '{{context.cart.total}}',\n },\n validateTenantMatch: true,\n },\n timeout: 'PT10S',\n async: false,\n retryPolicy: { maxAttempts: 3, initialIntervalMs: 2000, backoffCoefficient: 2, maxIntervalMs: 10000 },\n },\n {\n activityId: 'send_confirmation_email',\n activityName: 'Send Order Confirmation Email',\n activityType: 'SEND_EMAIL',\n config: {\n to: '{{context.customer.email}}',\n subject: 'Order Confirmation',\n template: 'order_confirmation',\n data: { customerName: '{{context.customer.name}}', total: '{{context.cart.total}}' },\n },\n timeout: 'PT15S',\n async: true,\n retryPolicy: { maxAttempts: 3, initialIntervalMs: 2000, backoffCoefficient: 2, maxIntervalMs: 20000 },\n },\n ],\n },\n ],\n})\n\n// ============================================================================\n// Module Export\n// ============================================================================\n\nexport const workflowsConfig = createWorkflowsModuleConfig({\n moduleId: 'workflows',\n workflows: [simpleApproval, checkoutDemo],\n})\n\nexport default workflowsConfig\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * Workflows Module \u2014 Code-Based Workflow Definitions\n *\n * Demo workflows shipped with the workflows module.\n * These are auto-discovered by the generator and loaded into the in-memory registry.\n */\n\nimport { defineWorkflow, createWorkflowsModuleConfig } from '@open-mercato/shared/modules/workflows'\n\n// ============================================================================\n// Simple Approval Workflow\n// ============================================================================\n\nconst simpleApproval = defineWorkflow({\n workflowId: 'workflows.simple-approval',\n workflowName: 'Simple Approval Workflow',\n description: 'Basic three-step approval workflow: Start \u2192 Approval \u2192 End',\n metadata: { category: 'Testing', tags: ['test', 'approval', 'simple'], icon: 'check-circle' },\n steps: [\n { stepId: 'start', stepName: 'Start', stepType: 'START', description: 'Initialize workflow' },\n {\n stepId: 'approval',\n stepName: 'Pending Approval',\n stepType: 'USER_TASK',\n description: 'Approve or reject request',\n userTaskConfig: { assignedTo: 'approver', slaDuration: 'PT1H' },\n },\n { stepId: 'end', stepName: 'Complete', stepType: 'END', description: 'Workflow complete' },\n ] as const,\n transitions: [\n {\n transitionId: 'start_to_approval',\n transitionName: 'Submit for Approval',\n fromStepId: 'start',\n toStepId: 'approval',\n trigger: 'auto',\n priority: 100,\n activities: [\n {\n activityId: 'notify_approver',\n activityName: 'Notify Approver',\n activityType: 'SEND_EMAIL',\n config: {\n to: '{{context.approver.email}}',\n subject: 'Approval Required: {{context.request.title}}',\n template: 'approval_request',\n data: {\n requestId: '{{context.request.id}}',\n title: '{{context.request.title}}',\n requester: '{{context.requester.name}}',\n },\n },\n timeout: 'PT10S',\n async: true,\n retryPolicy: { maxAttempts: 3, initialIntervalMs: 2000, backoffCoefficient: 2, maxIntervalMs: 20000 },\n },\n {\n activityId: 'emit_approval_requested',\n activityName: 'Emit Approval Requested Event',\n activityType: 'EMIT_EVENT',\n config: {\n eventType: 'approval.requested',\n payload: { requestId: '{{context.request.id}}', workflowInstanceId: '{{workflow.instanceId}}' },\n },\n async: true,\n },\n ],\n },\n {\n transitionId: 'approval_to_end',\n transitionName: 'Approval Decision',\n fromStepId: 'approval',\n toStepId: 'end',\n trigger: 'manual',\n priority: 100,\n activities: [\n {\n activityId: 'notify_requester',\n activityName: 'Notify Requester of Decision',\n activityType: 'SEND_EMAIL',\n config: {\n to: '{{context.requester.email}}',\n subject: 'Approval Decision: {{context.request.title}}',\n template: 'approval_decision',\n data: {\n requestId: '{{context.request.id}}',\n title: '{{context.request.title}}',\n decision: '{{task.result.approved}}',\n comments: '{{task.result.comments}}',\n approver: '{{task.completedBy}}',\n },\n },\n timeout: 'PT10S',\n async: true,\n },\n {\n activityId: 'emit_approval_completed',\n activityName: 'Emit Approval Completed Event',\n activityType: 'EMIT_EVENT',\n config: {\n eventType: 'approval.completed',\n payload: {\n requestId: '{{context.request.id}}',\n approved: '{{task.result.approved}}',\n approvedBy: '{{task.completedBy}}',\n timestamp: '{{now}}',\n },\n },\n async: true,\n },\n ],\n },\n ],\n})\n\n// ============================================================================\n// Checkout Demo Workflow\n// ============================================================================\n\nconst checkoutDemo = defineWorkflow({\n workflowId: 'workflows.checkout-demo',\n workflowName: 'Checkout with Payment Webhook',\n description: 'Realistic checkout workflow with signal-based payment confirmation',\n metadata: { category: 'E-commerce', tags: ['checkout', 'demo', 'payments', 'signals'], icon: 'ShoppingCart' },\n steps: [\n {\n stepId: 'start',\n stepName: 'Start',\n stepType: 'START',\n description: 'Checkout process initiated',\n preConditions: [{\n ruleId: 'workflow_checkout_cart_not_empty',\n required: true,\n validationMessage: {\n en: 'Your shopping cart is empty. Please add items before starting checkout.',\n pl: 'Tw\u00F3j koszyk jest pusty. Dodaj produkty przed rozpocz\u0119ciem zakupu.',\n },\n }],\n },\n { stepId: 'cart_validation', stepName: 'Cart Validation', stepType: 'AUTOMATED', description: 'Validate cart has items and inventory is available', timeout: 'PT10S' },\n {\n stepId: 'customer_info',\n stepName: 'Customer Information',\n stepType: 'USER_TASK',\n description: 'Collect customer shipping and contact information',\n userTaskConfig: {\n formSchema: {\n type: 'object',\n properties: {\n fullName: { type: 'string', title: 'Full Name', description: \"Customer's full legal name\" },\n email: { type: 'string', format: 'email', title: 'Email Address', description: 'Email for order confirmation' },\n phone: { type: 'string', title: 'Phone Number', description: 'Contact number for delivery' },\n comment: { type: 'string', title: 'Order Comment', maxLength: 500, description: 'Special instructions or notes' },\n },\n required: ['fullName', 'email'],\n },\n slaDuration: 'PT24H',\n },\n },\n { stepId: 'payment_initiation', stepName: 'Initiate Payment', stepType: 'AUTOMATED', description: 'Send payment request to payment provider', timeout: 'PT10S' },\n {\n stepId: 'wait_payment_confirmation',\n stepName: 'Wait for Payment Confirmation',\n stepType: 'WAIT_FOR_SIGNAL',\n description: 'Waiting for payment provider webhook confirmation',\n signalConfig: { signalName: 'payment_confirmed', timeout: 'PT5M' },\n },\n { stepId: 'order_confirmation', stepName: 'Order Confirmation', stepType: 'AUTOMATED', description: 'Create order record and send confirmation', timeout: 'PT15S' },\n { stepId: 'end', stepName: 'Complete', stepType: 'END', description: 'Checkout completed successfully' },\n ] as const,\n transitions: [\n {\n transitionId: 'start_to_cart',\n transitionName: 'Initialize Checkout',\n fromStepId: 'start',\n toStepId: 'cart_validation',\n trigger: 'auto',\n priority: 100,\n activities: [{\n activityId: 'log_checkout_start',\n activityName: 'Log Checkout Start',\n activityType: 'EMIT_EVENT',\n config: {\n eventName: 'checkout.started',\n payload: { customerId: '{{context.customerId}}', cartId: '{{context.cartId}}', currency: '{{context.currency}}', timestamp: '{{now}}' },\n },\n async: false,\n }],\n },\n {\n transitionId: 'cart_to_customer_info',\n transitionName: 'Collect Customer Information',\n fromStepId: 'cart_validation',\n toStepId: 'customer_info',\n trigger: 'auto',\n priority: 100,\n preConditions: [{ ruleId: 'workflow_checkout_cart_not_empty', required: true }],\n },\n {\n transitionId: 'customer_info_to_payment',\n transitionName: 'Initiate Payment',\n fromStepId: 'customer_info',\n toStepId: 'payment_initiation',\n trigger: 'auto',\n priority: 100,\n },\n {\n transitionId: 'payment_to_wait_confirmation',\n transitionName: 'Wait for Confirmation',\n fromStepId: 'payment_initiation',\n toStepId: 'wait_payment_confirmation',\n trigger: 'auto',\n priority: 100,\n },\n {\n transitionId: 'confirmation_to_order',\n transitionName: 'Payment Confirmed',\n fromStepId: 'wait_payment_confirmation',\n toStepId: 'order_confirmation',\n trigger: 'auto',\n priority: 100,\n },\n {\n transitionId: 'confirmation_to_end',\n transitionName: 'Finalize Order',\n fromStepId: 'order_confirmation',\n toStepId: 'end',\n trigger: 'auto',\n priority: 100,\n activities: [\n {\n activityId: 'create_order',\n activityName: 'Create Order Record',\n activityType: 'CALL_API',\n config: {\n endpoint: '/api/sales/orders',\n method: 'POST',\n body: {\n customerEntityId: '{{context.customer.id}}',\n currencyCode: '{{context.cart.currency}}',\n placedAt: '{{now}}',\n lines: '{{context.cart.orderLines}}',\n adjustments: '{{context.cart.orderAdjustments}}',\n grandTotalGrossAmount: '{{context.cart.total}}',\n },\n validateTenantMatch: true,\n },\n timeout: 'PT10S',\n async: false,\n retryPolicy: { maxAttempts: 3, initialIntervalMs: 2000, backoffCoefficient: 2, maxIntervalMs: 10000 },\n },\n {\n activityId: 'send_confirmation_email',\n activityName: 'Send Order Confirmation Email',\n activityType: 'SEND_EMAIL',\n config: {\n to: '{{context.customer.email}}',\n subject: 'Order Confirmation',\n template: 'order_confirmation',\n data: { customerName: '{{context.customer.name}}', total: '{{context.cart.total}}' },\n },\n timeout: 'PT15S',\n async: true,\n retryPolicy: { maxAttempts: 3, initialIntervalMs: 2000, backoffCoefficient: 2, maxIntervalMs: 20000 },\n },\n ],\n },\n ],\n})\n\n// ============================================================================\n// Module Export\n// ============================================================================\n\nexport const workflowsConfig = createWorkflowsModuleConfig({\n moduleId: 'workflows',\n workflows: [simpleApproval, checkoutDemo],\n})\n\nexport default workflowsConfig\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.6
|
|
3
|
+
"version": "0.6.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -253,16 +253,16 @@
|
|
|
253
253
|
"zod": "^4.4.3"
|
|
254
254
|
},
|
|
255
255
|
"peerDependencies": {
|
|
256
|
-
"@open-mercato/ai-assistant": "0.6.6
|
|
257
|
-
"@open-mercato/shared": "0.6.6
|
|
258
|
-
"@open-mercato/ui": "0.6.6
|
|
256
|
+
"@open-mercato/ai-assistant": "0.6.6",
|
|
257
|
+
"@open-mercato/shared": "0.6.6",
|
|
258
|
+
"@open-mercato/ui": "0.6.6",
|
|
259
259
|
"react": "^19.0.0",
|
|
260
260
|
"react-dom": "^19.0.0"
|
|
261
261
|
},
|
|
262
262
|
"devDependencies": {
|
|
263
|
-
"@open-mercato/ai-assistant": "0.6.6
|
|
264
|
-
"@open-mercato/shared": "0.6.6
|
|
265
|
-
"@open-mercato/ui": "0.6.6
|
|
263
|
+
"@open-mercato/ai-assistant": "0.6.6",
|
|
264
|
+
"@open-mercato/shared": "0.6.6",
|
|
265
|
+
"@open-mercato/ui": "0.6.6",
|
|
266
266
|
"@testing-library/dom": "^10.4.1",
|
|
267
267
|
"@testing-library/jest-dom": "^6.9.1",
|
|
268
268
|
"@testing-library/react": "^16.3.1",
|
|
@@ -285,6 +285,5 @@
|
|
|
285
285
|
"type": "git",
|
|
286
286
|
"url": "https://github.com/open-mercato/open-mercato",
|
|
287
287
|
"directory": "packages/core"
|
|
288
|
-
}
|
|
289
|
-
"stableVersion": "0.6.5"
|
|
288
|
+
}
|
|
290
289
|
}
|
|
@@ -13,8 +13,6 @@ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
|
13
13
|
import { enforceTenantSelection, resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'
|
|
14
14
|
import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
|
|
15
15
|
import { assertActorCanGrantRoles } from '@open-mercato/core/modules/auth/lib/grantChecks'
|
|
16
|
-
import { isOrganizationAccessAllowed } from '@open-mercato/shared/lib/auth/organizationAccess'
|
|
17
|
-
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
18
16
|
|
|
19
17
|
type ApiKeyCrudCtx = CrudCtx & {
|
|
20
18
|
__apiKeySecret?: { secret: string; prefix: string }
|
|
@@ -324,34 +322,18 @@ const crud = makeCrudRoute<
|
|
|
324
322
|
const { translate } = await resolveTranslations()
|
|
325
323
|
if (!auth?.tenantId) throw json({ error: translate('api_keys.errors.tenantRequired', 'Tenant context required') }, { status: 400 })
|
|
326
324
|
const em = (ctx.container.resolve('em') as EntityManager)
|
|
325
|
+
const record = await em.findOne(ApiKey, { id, deletedAt: null })
|
|
326
|
+
if (!record) throw json({ error: translate('api_keys.errors.notFound', 'Not found') }, { status: 404 })
|
|
327
327
|
const scopedCtx = ctx as ApiKeyCrudCtx
|
|
328
328
|
const isSuperAdmin = await resolveIsSuperAdmin(scopedCtx)
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
id,
|
|
332
|
-
tenantId: auth.tenantId,
|
|
333
|
-
deletedAt: null,
|
|
334
|
-
}
|
|
335
|
-
if (!isSuperAdmin && Array.isArray(allowedIds)) {
|
|
336
|
-
recordFilter.organizationId = { $in: allowedIds }
|
|
329
|
+
if (!isSuperAdmin && record.tenantId && record.tenantId !== auth.tenantId) {
|
|
330
|
+
throw json({ error: translate('api_keys.errors.forbidden', 'Forbidden') }, { status: 403 })
|
|
337
331
|
}
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
{ tenantId: auth.tenantId, organizationId: null },
|
|
344
|
-
)
|
|
345
|
-
if (
|
|
346
|
-
!record ||
|
|
347
|
-
record.tenantId !== auth.tenantId ||
|
|
348
|
-
!isOrganizationAccessAllowed({
|
|
349
|
-
isSuperAdmin,
|
|
350
|
-
allowedOrganizationIds: allowedIds,
|
|
351
|
-
targetOrganizationId: record.organizationId ?? null,
|
|
352
|
-
})
|
|
353
|
-
) {
|
|
354
|
-
throw json({ error: translate('api_keys.errors.notFound', 'Not found') }, { status: 404 })
|
|
332
|
+
const allowedIds = ctx.organizationScope?.allowedIds ?? null
|
|
333
|
+
if (record.organizationId && Array.isArray(allowedIds) && allowedIds.length > 0) {
|
|
334
|
+
if (!allowedIds.includes(record.organizationId)) {
|
|
335
|
+
throw json({ error: translate('api_keys.errors.organizationOutOfScope', 'Organization out of scope') }, { status: 403 })
|
|
336
|
+
}
|
|
355
337
|
}
|
|
356
338
|
scopedCtx.__apiKeyOrganizationId = record.organizationId ?? null
|
|
357
339
|
},
|
|
@@ -3,7 +3,6 @@ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
|
3
3
|
import { createApiKey } from './services/apiKeyService'
|
|
4
4
|
import { Organization } from '@open-mercato/core/modules/directory/data/entities'
|
|
5
5
|
import { Role } from '@open-mercato/core/modules/auth/data/entities'
|
|
6
|
-
import { parseCommaSeparatedList } from '@open-mercato/shared/lib/string'
|
|
7
6
|
|
|
8
7
|
function parseArgs(rest: string[]): Record<string, string> {
|
|
9
8
|
const args: Record<string, string> = {}
|
|
@@ -48,7 +47,9 @@ const addApiKey: ModuleCli = {
|
|
|
48
47
|
return
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
const roleInputs =
|
|
50
|
+
const roleInputs = rolesCsv
|
|
51
|
+
? rolesCsv.split(',').map((value) => value.trim()).filter(Boolean)
|
|
52
|
+
: []
|
|
52
53
|
const roleIds: string[] = []
|
|
53
54
|
if (roleInputs.length) {
|
|
54
55
|
for (const token of roleInputs) {
|
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
canRenderInlineAttachment,
|
|
16
16
|
} from "@open-mercato/core/modules/attachments/lib/security";
|
|
17
17
|
import { StorageDriverFactory } from '../../../lib/drivers';
|
|
18
|
-
import { resolveAttachmentOrganizationId } from '@open-mercato/core/modules/attachments/lib/requestScope';
|
|
19
18
|
|
|
20
19
|
export const metadata = {
|
|
21
20
|
GET: { requireAuth: false },
|
|
@@ -33,19 +32,16 @@ export async function GET(
|
|
|
33
32
|
);
|
|
34
33
|
}
|
|
35
34
|
const auth = await getAuthFromRequest(req);
|
|
36
|
-
const
|
|
37
|
-
const em =
|
|
35
|
+
const { resolve } = await createRequestContainer();
|
|
36
|
+
const em = resolve("em") as EntityManager;
|
|
38
37
|
const storageDriverFactory =
|
|
39
|
-
(
|
|
38
|
+
(resolve("storageDriverFactory") as StorageDriverFactory | null) ??
|
|
40
39
|
new StorageDriverFactory(em);
|
|
41
40
|
|
|
42
|
-
const scopedAuth = auth
|
|
43
|
-
? { ...auth, orgId: await resolveAttachmentOrganizationId(container, auth, req) }
|
|
44
|
-
: auth;
|
|
45
41
|
const findFilter: Record<string, unknown> = { id };
|
|
46
|
-
if (
|
|
47
|
-
if (
|
|
48
|
-
if (
|
|
42
|
+
if (auth && !isSuperAdminAuth(auth)) {
|
|
43
|
+
if (auth.tenantId) findFilter.tenantId = auth.tenantId;
|
|
44
|
+
if (auth.orgId) findFilter.organizationId = auth.orgId;
|
|
49
45
|
}
|
|
50
46
|
const attachment = await em.findOne(Attachment, findFilter);
|
|
51
47
|
if (!attachment) {
|
|
@@ -64,7 +60,7 @@ export async function GET(
|
|
|
64
60
|
);
|
|
65
61
|
}
|
|
66
62
|
|
|
67
|
-
const access = checkAttachmentAccess(
|
|
63
|
+
const access = checkAttachmentAccess(auth, attachment, partition);
|
|
68
64
|
if (!access.ok) {
|
|
69
65
|
const message = access.status === 401 ? "Unauthorized" : "Forbidden";
|
|
70
66
|
return NextResponse.json({ error: message }, { status: access.status });
|
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
validateImageMagicBytes,
|
|
21
21
|
} from '@open-mercato/core/modules/attachments/lib/imageSafety'
|
|
22
22
|
import { StorageDriverFactory } from '../../../../lib/drivers'
|
|
23
|
-
import { resolveAttachmentOrganizationId } from '@open-mercato/core/modules/attachments/lib/requestScope'
|
|
24
23
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
25
24
|
|
|
26
25
|
const logger = createLogger('attachments').child({ component: 'image' })
|
|
@@ -52,18 +51,15 @@ export async function GET(
|
|
|
52
51
|
}
|
|
53
52
|
const { width, height, cropType } = parsedQuery.data
|
|
54
53
|
|
|
55
|
-
const
|
|
56
|
-
const em =
|
|
54
|
+
const { resolve } = await createRequestContainer()
|
|
55
|
+
const em = resolve('em') as EntityManager
|
|
57
56
|
const storageDriverFactory =
|
|
58
|
-
(
|
|
57
|
+
(resolve('storageDriverFactory') as StorageDriverFactory | null) ?? new StorageDriverFactory(em)
|
|
59
58
|
|
|
60
|
-
const scopedAuth = auth
|
|
61
|
-
? { ...auth, orgId: await resolveAttachmentOrganizationId(container, auth, req) }
|
|
62
|
-
: auth
|
|
63
59
|
const findFilter: Record<string, unknown> = { id }
|
|
64
|
-
if (
|
|
65
|
-
if (
|
|
66
|
-
if (
|
|
60
|
+
if (auth && !isSuperAdminAuth(auth)) {
|
|
61
|
+
if (auth.tenantId) findFilter.tenantId = auth.tenantId
|
|
62
|
+
if (auth.orgId) findFilter.organizationId = auth.orgId
|
|
67
63
|
}
|
|
68
64
|
const attachment = await em.findOne(Attachment, findFilter)
|
|
69
65
|
if (!attachment) {
|
|
@@ -76,7 +72,7 @@ export async function GET(
|
|
|
76
72
|
if (!partition) {
|
|
77
73
|
return NextResponse.json({ error: 'Partition misconfigured' }, { status: 500 })
|
|
78
74
|
}
|
|
79
|
-
const access = checkAttachmentAccess(
|
|
75
|
+
const access = checkAttachmentAccess(auth, attachment, partition)
|
|
80
76
|
if (!access.ok) {
|
|
81
77
|
const message = access.status === 401 ? 'Unauthorized' : 'Forbidden'
|
|
82
78
|
return NextResponse.json({ error: message }, { status: access.status })
|
|
@@ -13,7 +13,6 @@ import { StorageDriverFactory } from '../lib/drivers'
|
|
|
13
13
|
import { OcrService, shouldUseLlmOcr } from '../lib/ocrService'
|
|
14
14
|
import { clearAttachmentThumbnailCache } from '../lib/thumbnailCache'
|
|
15
15
|
import { assertAttachmentScopeInvariant } from '../lib/access'
|
|
16
|
-
import { resolveAttachmentOrganizationId } from '../lib/requestScope'
|
|
17
16
|
import {
|
|
18
17
|
mergeAttachmentMetadata,
|
|
19
18
|
normalizeAttachmentAssignments,
|
|
@@ -199,11 +198,10 @@ export async function GET(req: Request) {
|
|
|
199
198
|
}
|
|
200
199
|
const { entityId, recordId, page, pageSize } = parsedQuery.data
|
|
201
200
|
|
|
202
|
-
const
|
|
203
|
-
const em =
|
|
204
|
-
const orgId = await resolveAttachmentOrganizationId(container, auth, req)
|
|
201
|
+
const { resolve } = await createRequestContainer()
|
|
202
|
+
const em = resolve('em') as EntityManager
|
|
205
203
|
const filter: Record<string, unknown> = { entityId, recordId, tenantId: auth.tenantId! }
|
|
206
|
-
if (orgId) filter.organizationId = orgId
|
|
204
|
+
if (auth.orgId) filter.organizationId = auth.orgId
|
|
207
205
|
const orderBy: Record<string, 'ASC' | 'DESC'> = { createdAt: 'DESC' }
|
|
208
206
|
const usePaging = typeof page === 'number' && typeof pageSize === 'number'
|
|
209
207
|
const total = usePaging ? await em.count(Attachment, filter) : null
|
|
@@ -226,7 +224,7 @@ export async function GET(req: Request) {
|
|
|
226
224
|
},
|
|
227
225
|
{
|
|
228
226
|
tenantId: auth.tenantId ?? null,
|
|
229
|
-
organizationId: orgId ?? null,
|
|
227
|
+
organizationId: auth.orgId ?? null,
|
|
230
228
|
},
|
|
231
229
|
)
|
|
232
230
|
return NextResponse.json({
|
|
@@ -277,6 +275,7 @@ export async function POST(req: Request) {
|
|
|
277
275
|
}, { status: 400 })
|
|
278
276
|
}
|
|
279
277
|
const tenantId = auth.tenantId
|
|
278
|
+
const orgId = auth.orgId
|
|
280
279
|
|
|
281
280
|
const contentType = req.headers.get('content-type') || ''
|
|
282
281
|
if (!contentType.toLowerCase().includes('multipart/form-data')) {
|
|
@@ -302,13 +301,11 @@ export async function POST(req: Request) {
|
|
|
302
301
|
const tags = parseFormTags(form.get('tags'))
|
|
303
302
|
const assignmentsFromForm = parseFormAssignments(form.get('assignments'))
|
|
304
303
|
|
|
305
|
-
const
|
|
306
|
-
const em =
|
|
307
|
-
const dataEngine =
|
|
304
|
+
const { resolve } = await createRequestContainer()
|
|
305
|
+
const em = resolve('em') as EntityManager
|
|
306
|
+
const dataEngine = resolve('dataEngine')
|
|
308
307
|
const storageDriverFactory =
|
|
309
|
-
(
|
|
310
|
-
const orgId = await resolveAttachmentOrganizationId(container, auth, req)
|
|
311
|
-
if (!orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
308
|
+
(resolve('storageDriverFactory') as StorageDriverFactory | null) ?? new StorageDriverFactory(em)
|
|
312
309
|
await ensureDefaultPartitions(em)
|
|
313
310
|
// Optional per-field validations
|
|
314
311
|
let partitionFromField: string | null = null
|
|
@@ -439,12 +436,12 @@ export async function POST(req: Request) {
|
|
|
439
436
|
}
|
|
440
437
|
const metadata = mergeAttachmentMetadata(null, { assignments, tags })
|
|
441
438
|
const attachmentId = randomUUID()
|
|
442
|
-
assertAttachmentScopeInvariant({ tenantId: auth.tenantId, organizationId: orgId })
|
|
439
|
+
assertAttachmentScopeInvariant({ tenantId: auth.tenantId, organizationId: auth.orgId })
|
|
443
440
|
const att = em.create(Attachment, {
|
|
444
441
|
id: attachmentId,
|
|
445
442
|
entityId,
|
|
446
443
|
recordId,
|
|
447
|
-
organizationId: orgId
|
|
444
|
+
organizationId: auth.orgId!,
|
|
448
445
|
tenantId: auth.tenantId!,
|
|
449
446
|
fileName: safeName,
|
|
450
447
|
mimeType: fileMimeType,
|
|
@@ -548,19 +545,17 @@ export async function DELETE(req: Request) {
|
|
|
548
545
|
const url = new URL(req.url)
|
|
549
546
|
const id = url.searchParams.get('id') || ''
|
|
550
547
|
if (!id) return NextResponse.json({ error: 'Attachment id is required' }, { status: 400 })
|
|
551
|
-
const
|
|
552
|
-
const em =
|
|
553
|
-
const dataEngine =
|
|
548
|
+
const { resolve } = await createRequestContainer()
|
|
549
|
+
const em = resolve('em') as EntityManager
|
|
550
|
+
const dataEngine = resolve('dataEngine')
|
|
554
551
|
const storageDriverFactory =
|
|
555
|
-
(
|
|
556
|
-
//
|
|
557
|
-
//
|
|
558
|
-
//
|
|
559
|
-
//
|
|
560
|
-
// (#3764) — letting them remove any attachment in the tenant.
|
|
561
|
-
const orgId = await resolveAttachmentOrganizationId(container, auth, req)
|
|
552
|
+
(resolve('storageDriverFactory') as StorageDriverFactory | null) ?? new StorageDriverFactory(em)
|
|
553
|
+
// Mirror the GET handler's superadmin bypass: a superadmin browsing with "All
|
|
554
|
+
// organizations" selected has no concrete org, so scope the delete by tenant
|
|
555
|
+
// only and let them remove any attachment in the tenant. Non-superadmins stay
|
|
556
|
+
// scoped to their own organization (#3764).
|
|
562
557
|
const deleteFilter: Record<string, unknown> = { id, tenantId: auth.tenantId! }
|
|
563
|
-
if (orgId) deleteFilter.organizationId = orgId
|
|
558
|
+
if (auth.orgId) deleteFilter.organizationId = auth.orgId
|
|
564
559
|
const record = await em.findOne(Attachment, deleteFilter)
|
|
565
560
|
if (!record) return NextResponse.json({ error: 'Attachment not found' }, { status: 404 })
|
|
566
561
|
await em.remove(record).flush()
|
|
@@ -570,7 +565,7 @@ export async function DELETE(req: Request) {
|
|
|
570
565
|
if (record.storagePath) {
|
|
571
566
|
const delDriver = await storageDriverFactory.resolveForPartition(record.partitionCode, {
|
|
572
567
|
tenantId: record.tenantId ?? auth.tenantId!,
|
|
573
|
-
organizationId: record.organizationId ?? orgId ?? '',
|
|
568
|
+
organizationId: record.organizationId ?? auth.orgId ?? '',
|
|
574
569
|
})
|
|
575
570
|
await delDriver.delete(record.partitionCode, record.storagePath)
|
|
576
571
|
}
|
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
23
23
|
import { buildPasswordSchema } from '@open-mercato/shared/lib/auth/passwordPolicy'
|
|
24
24
|
import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
|
|
25
|
-
import { parseBooleanFlag } from '@open-mercato/shared/lib/boolean'
|
|
26
25
|
import { resolveSearchConfig } from '@open-mercato/shared/lib/search/config'
|
|
27
26
|
import { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'
|
|
28
27
|
import { sql } from 'kysely'
|
|
@@ -42,7 +41,6 @@ const querySchema = z.object({
|
|
|
42
41
|
search: z.string().optional(),
|
|
43
42
|
name: z.string().optional(),
|
|
44
43
|
organizationId: z.string().uuid().optional(),
|
|
45
|
-
scopeToActiveOrganization: z.boolean().optional(),
|
|
46
44
|
roleIds: z.array(z.string().uuid()).optional(),
|
|
47
45
|
}).passthrough()
|
|
48
46
|
|
|
@@ -182,7 +180,6 @@ export async function GET(req: Request) {
|
|
|
182
180
|
search: url.searchParams.get('search') || undefined,
|
|
183
181
|
name: url.searchParams.get('name') || undefined,
|
|
184
182
|
organizationId: url.searchParams.get('organizationId') || undefined,
|
|
185
|
-
scopeToActiveOrganization: parseBooleanFlag(url.searchParams.get('scopeToActiveOrganization') || undefined),
|
|
186
183
|
roleIds: rawRoleIds.length ? rawRoleIds : undefined,
|
|
187
184
|
})
|
|
188
185
|
if (!parsed.success) return NextResponse.json({ items: [], total: 0, totalPages: 1 })
|
|
@@ -198,7 +195,7 @@ export async function GET(req: Request) {
|
|
|
198
195
|
} catch (err) {
|
|
199
196
|
logger.error('Failed to resolve rbac', { err })
|
|
200
197
|
}
|
|
201
|
-
const { id, page, pageSize, search, name, organizationId,
|
|
198
|
+
const { id, page, pageSize, search, name, organizationId, roleIds } = parsed.data
|
|
202
199
|
const filters: any[] = [{ deletedAt: null }]
|
|
203
200
|
const actorTenantId = auth.tenantId ? String(auth.tenantId) : null
|
|
204
201
|
let effectiveTenantId: string | null = null
|
|
@@ -247,12 +244,6 @@ export async function GET(req: Request) {
|
|
|
247
244
|
? effectiveSelectedOrganizationId
|
|
248
245
|
: auth.orgId ?? null
|
|
249
246
|
if (organizationId) filters.push({ organizationId })
|
|
250
|
-
// Recipient/assignee pickers scope to the caller's active organization so they never
|
|
251
|
-
// suggest users outside it. A message composed here is stamped with the caller's
|
|
252
|
-
// active org (auth.orgId), and the message detail endpoint enforces
|
|
253
|
-
// hasOrganizationAccess(scope.organizationId, message.organizationId); scoping the
|
|
254
|
-
// suggestions to the same org keeps a picked recipient able to open what they were sent.
|
|
255
|
-
if (scopeToActiveOrganization) filters.push({ organizationId: auth.orgId ?? null })
|
|
256
247
|
const trimmedName = typeof name === 'string' ? name.trim() : ''
|
|
257
248
|
if (trimmedName) {
|
|
258
249
|
const searchPattern = `%${escapeLikePattern(trimmedName)}%`
|
|
@@ -629,7 +620,7 @@ export const openApi: OpenApiRouteDoc = {
|
|
|
629
620
|
GET: {
|
|
630
621
|
summary: 'List users',
|
|
631
622
|
description:
|
|
632
|
-
'Returns users for the effective selected tenant and organization scope. Search matches email, organization name, and role name. Super administrators may scope the response via the topbar context, organization filters, or role filters.
|
|
623
|
+
'Returns users for the effective selected tenant and organization scope. Search matches email, organization name, and role name. Super administrators may scope the response via the topbar context, organization filters, or role filters.',
|
|
633
624
|
query: querySchema,
|
|
634
625
|
responses: [
|
|
635
626
|
{ status: 200, description: 'User collection', schema: userListResponseSchema },
|
package/src/modules/auth/cli.ts
CHANGED
|
@@ -9,7 +9,6 @@ import { Tenant, Organization } from '@open-mercato/core/modules/directory/data/
|
|
|
9
9
|
import { rebuildHierarchyForTenant } from '@open-mercato/core/modules/directory/lib/hierarchy'
|
|
10
10
|
import { ensureRoles, setupInitialTenant, ensureDefaultRoleAcls, ensureCustomRoleAcls, OrgSlugExistsError, DerivedUserPasswordRequiredError } from './lib/setup-app'
|
|
11
11
|
import { normalizeTenantId } from './lib/tenantAccess'
|
|
12
|
-
import { parseCommaSeparatedList } from '@open-mercato/shared/lib/string'
|
|
13
12
|
import { computeEmailHash, emailHashLookupValues } from './lib/emailHash'
|
|
14
13
|
import { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
15
14
|
import { isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'
|
|
@@ -72,7 +71,7 @@ const addUser: ModuleCli = {
|
|
|
72
71
|
})
|
|
73
72
|
await em.persist(u).flush()
|
|
74
73
|
if (rolesCsv) {
|
|
75
|
-
const names =
|
|
74
|
+
const names = rolesCsv.split(',').map(s => s.trim()).filter(Boolean)
|
|
76
75
|
for (const name of names) {
|
|
77
76
|
const role = await resolveTenantScopedRole(em, name, normalizedTenantId)
|
|
78
77
|
const link = em.create(UserRole, { user: u, role })
|
|
@@ -494,7 +493,7 @@ const setupApp: ModuleCli = {
|
|
|
494
493
|
const container = await createRequestContainer()
|
|
495
494
|
const em = container.resolve<EntityManager>('em')
|
|
496
495
|
const roleNames = rolesCsv
|
|
497
|
-
?
|
|
496
|
+
? rolesCsv.split(',').map((s) => s.trim()).filter(Boolean)
|
|
498
497
|
: undefined
|
|
499
498
|
|
|
500
499
|
try {
|
|
@@ -189,9 +189,5 @@
|
|
|
189
189
|
"configs.systemStatus.variables.queryIndexWarmupThrottleMs.label": "Warmup-Drosselfenster",
|
|
190
190
|
"configs.systemStatus.variables.scheduleAutoReindex.description": "Automatisch Query-Index-Neuindizierungsaufträge bei Lücken planen.",
|
|
191
191
|
"configs.systemStatus.variables.scheduleAutoReindex.label": "Automatische Neuindizierung",
|
|
192
|
-
"configs.systemStatus.viewDocs": "Dokumentation anzeigen"
|
|
193
|
-
"configs.upgrades.attachmentsOrgReconcile.cta": "Anhang-Organisationen abgleichen",
|
|
194
|
-
"configs.upgrades.attachmentsOrgReconcile.loading": "Anhang-Organisationen werden abgeglichen…",
|
|
195
|
-
"configs.upgrades.attachmentsOrgReconcile.message": "Version {{version}} behebt Anhänge, die unter der falschen Organisation gespeichert wurden. Führen Sie den Abgleich aus, um vorhandene Anhänge in die Organisation des zugehörigen Datensatzes zu verschieben.",
|
|
196
|
-
"configs.upgrades.attachmentsOrgReconcile.success": "Anhang-Organisationen abgeglichen."
|
|
192
|
+
"configs.systemStatus.viewDocs": "Dokumentation anzeigen"
|
|
197
193
|
}
|
|
@@ -189,9 +189,5 @@
|
|
|
189
189
|
"configs.systemStatus.variables.queryIndexWarmupThrottleMs.label": "Warmup throttle window",
|
|
190
190
|
"configs.systemStatus.variables.scheduleAutoReindex.description": "Automatically schedule query index reindex jobs when gaps appear.",
|
|
191
191
|
"configs.systemStatus.variables.scheduleAutoReindex.label": "Auto reindex",
|
|
192
|
-
"configs.systemStatus.viewDocs": "View documentation"
|
|
193
|
-
"configs.upgrades.attachmentsOrgReconcile.cta": "Reconcile attachment organizations",
|
|
194
|
-
"configs.upgrades.attachmentsOrgReconcile.loading": "Reconciling attachment organizations…",
|
|
195
|
-
"configs.upgrades.attachmentsOrgReconcile.message": "Version {{version}} fixes attachments that were saved under the wrong organization. Run the reconciliation to move existing attachments to the organization of the record they belong to.",
|
|
196
|
-
"configs.upgrades.attachmentsOrgReconcile.success": "Attachment organizations reconciled."
|
|
192
|
+
"configs.systemStatus.viewDocs": "View documentation"
|
|
197
193
|
}
|
|
@@ -189,9 +189,5 @@
|
|
|
189
189
|
"configs.systemStatus.variables.queryIndexWarmupThrottleMs.label": "Ventana de limitación del calentamiento",
|
|
190
190
|
"configs.systemStatus.variables.scheduleAutoReindex.description": "Programar automáticamente trabajos de reindexación cuando se detecten brechas.",
|
|
191
191
|
"configs.systemStatus.variables.scheduleAutoReindex.label": "Reindexación automática",
|
|
192
|
-
"configs.systemStatus.viewDocs": "Ver documentación"
|
|
193
|
-
"configs.upgrades.attachmentsOrgReconcile.cta": "Reconciliar organizaciones de adjuntos",
|
|
194
|
-
"configs.upgrades.attachmentsOrgReconcile.loading": "Reconciliando organizaciones de adjuntos…",
|
|
195
|
-
"configs.upgrades.attachmentsOrgReconcile.message": "La versión {{version}} corrige los adjuntos guardados en la organización incorrecta. Ejecuta la reconciliación para mover los adjuntos existentes a la organización del registro al que pertenecen.",
|
|
196
|
-
"configs.upgrades.attachmentsOrgReconcile.success": "Organizaciones de adjuntos reconciliadas."
|
|
192
|
+
"configs.systemStatus.viewDocs": "Ver documentación"
|
|
197
193
|
}
|
|
@@ -189,9 +189,5 @@
|
|
|
189
189
|
"configs.systemStatus.variables.queryIndexWarmupThrottleMs.label": "Okno throttlingu rozgrzewki",
|
|
190
190
|
"configs.systemStatus.variables.scheduleAutoReindex.description": "Automatycznie uruchamia przebudowę indeksu zapytań, gdy wykryte zostaną luki.",
|
|
191
191
|
"configs.systemStatus.variables.scheduleAutoReindex.label": "Automatyczna przebudowa indeksu",
|
|
192
|
-
"configs.systemStatus.viewDocs": "Zobacz dokumentację"
|
|
193
|
-
"configs.upgrades.attachmentsOrgReconcile.cta": "Uzgodnij organizacje załączników",
|
|
194
|
-
"configs.upgrades.attachmentsOrgReconcile.loading": "Uzgadnianie organizacji załączników…",
|
|
195
|
-
"configs.upgrades.attachmentsOrgReconcile.message": "Wersja {{version}} naprawia załączniki zapisane pod niewłaściwą organizacją. Uruchom uzgadnianie, aby przenieść istniejące załączniki do organizacji rekordu, do którego należą.",
|
|
196
|
-
"configs.upgrades.attachmentsOrgReconcile.success": "Organizacje załączników zostały uzgodnione."
|
|
192
|
+
"configs.systemStatus.viewDocs": "Zobacz dokumentację"
|
|
197
193
|
}
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
2
|
import * as semver from 'semver'
|
|
3
3
|
import type { AppContainer } from '@open-mercato/shared/lib/di/container'
|
|
4
|
-
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
5
|
-
import type { QueryEngine } from '@open-mercato/shared/lib/query/types'
|
|
6
|
-
import { reconcileAttachmentOrganizations } from '@open-mercato/core/modules/attachments/lib/reconcileOrganization'
|
|
7
|
-
|
|
8
|
-
const logger = createLogger('configs').child({ component: 'upgrade-actions' })
|
|
9
4
|
|
|
10
5
|
export type UpgradeActionContext = {
|
|
11
6
|
tenantId: string
|
|
@@ -43,25 +38,6 @@ export function compareVersions(a: string, b: string): number {
|
|
|
43
38
|
}
|
|
44
39
|
|
|
45
40
|
export const upgradeActions: UpgradeActionDefinition[] = [
|
|
46
|
-
{
|
|
47
|
-
id: 'attachments.reconcile-organization',
|
|
48
|
-
version: '0.6.6',
|
|
49
|
-
messageKey: 'configs.upgrades.attachmentsOrgReconcile.message',
|
|
50
|
-
ctaKey: 'configs.upgrades.attachmentsOrgReconcile.cta',
|
|
51
|
-
successKey: 'configs.upgrades.attachmentsOrgReconcile.success',
|
|
52
|
-
loadingKey: 'configs.upgrades.attachmentsOrgReconcile.loading',
|
|
53
|
-
async run({ container, em, tenantId }) {
|
|
54
|
-
const queryEngine = container.resolve('queryEngine') as QueryEngine
|
|
55
|
-
const report = await reconcileAttachmentOrganizations({ em, queryEngine, tenantId })
|
|
56
|
-
logger.info('attachments organization reconcile completed', {
|
|
57
|
-
tenantId,
|
|
58
|
-
scanned: report.scanned,
|
|
59
|
-
updated: report.updated,
|
|
60
|
-
unresolved: report.unresolved,
|
|
61
|
-
skippedVirtual: report.skippedVirtual,
|
|
62
|
-
})
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
41
|
{
|
|
66
42
|
id: 'customers.seed-interaction-statuses',
|
|
67
43
|
version: '0.6.5',
|
|
@@ -306,17 +306,15 @@ Both link to `/backend/customer_accounts/{sourceEntityId}` for staff review.
|
|
|
306
306
|
|
|
307
307
|
| Spot ID | Widget | Purpose |
|
|
308
308
|
|---------|--------|---------|
|
|
309
|
-
| `customers.person` | `account-status` |
|
|
310
|
-
| `crud-form:customers.person` | `account-status` |
|
|
311
|
-
| `customers
|
|
312
|
-
| `
|
|
309
|
+
| `customers.person` | `account-status` | Shows portal account status on Customers v2 person detail forms |
|
|
310
|
+
| `crud-form:customers.person` | `account-status` | Alias for CrudForm hosts deriving the spot from `customers.person` |
|
|
311
|
+
| `crud-form:customers:customer_person_profile:fields` | `account-status` | Shows portal account status on CRM person detail page |
|
|
312
|
+
| `customers.company` | `company-users` | Shows portal users on Customers v2 company detail forms |
|
|
313
|
+
| `crud-form:customers.company` | `company-users` | Alias for CrudForm hosts deriving the spot from `customers.company` |
|
|
314
|
+
| `crud-form:customers:customer_company_profile:fields` | `company-users` | Shows portal users linked to a CRM company |
|
|
313
315
|
|
|
314
316
|
Both inject as column 2 groups with priority 200, gated by `customer_accounts.view` feature.
|
|
315
317
|
|
|
316
|
-
CrudForm derives its spot id as `crud-form:${entityId}` with every `:` normalized to `.`, so a
|
|
317
|
-
registered `crud-form:` key whose entity segment still contains `:` can never be requested by any
|
|
318
|
-
host. Register against the normalized (dot) form only — `injection-table.test.ts` enforces this.
|
|
319
|
-
|
|
320
318
|
## Backend Pages
|
|
321
319
|
|
|
322
320
|
| Path | Purpose |
|