@open-mercato/core 0.4.2-canary-7732371765 → 0.4.2-canary-ed15f2e753
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/dist/generated/entities/api_key/index.js +2 -0
- package/dist/generated/entities/api_key/index.js.map +2 -2
- package/dist/generated/entities/workflow_event_trigger/index.js +33 -0
- package/dist/generated/entities/workflow_event_trigger/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +1 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/api_keys/data/entities.js +3 -0
- package/dist/modules/api_keys/data/entities.js.map +2 -2
- package/dist/modules/api_keys/migrations/Migration20260125204102.js +13 -0
- package/dist/modules/api_keys/migrations/Migration20260125204102.js.map +7 -0
- package/dist/modules/api_keys/services/apiKeyService.js +41 -0
- package/dist/modules/api_keys/services/apiKeyService.js.map +3 -3
- package/dist/modules/auth/events.js +30 -0
- package/dist/modules/auth/events.js.map +7 -0
- package/dist/modules/auth/services/rbacService.js.map +2 -2
- package/dist/modules/business_rules/api/execute/[ruleId]/route.js +145 -0
- package/dist/modules/business_rules/api/execute/[ruleId]/route.js.map +7 -0
- package/dist/modules/business_rules/data/validators.js +34 -0
- package/dist/modules/business_rules/data/validators.js.map +2 -2
- package/dist/modules/business_rules/index.js +21 -1
- package/dist/modules/business_rules/index.js.map +2 -2
- package/dist/modules/business_rules/lib/rule-engine.js +182 -1
- package/dist/modules/business_rules/lib/rule-engine.js.map +2 -2
- package/dist/modules/catalog/events.js +34 -0
- package/dist/modules/catalog/events.js.map +7 -0
- package/dist/modules/customers/events.js +49 -0
- package/dist/modules/customers/events.js.map +7 -0
- package/dist/modules/directory/events.js +23 -0
- package/dist/modules/directory/events.js.map +7 -0
- package/dist/modules/sales/acl.js +1 -0
- package/dist/modules/sales/acl.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +12 -0
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +62 -0
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/events.js +63 -0
- package/dist/modules/sales/events.js.map +7 -0
- package/dist/modules/sales/lib/dictionaries.js +3 -0
- package/dist/modules/sales/lib/dictionaries.js.map +2 -2
- package/dist/modules/sales/lib/frontend/documentDataEvents.js +25 -0
- package/dist/modules/sales/lib/frontend/documentDataEvents.js.map +7 -0
- package/dist/modules/workflows/acl.js +2 -0
- package/dist/modules/workflows/acl.js.map +2 -2
- package/dist/modules/workflows/api/instances/route.js +18 -6
- package/dist/modules/workflows/api/instances/route.js.map +2 -2
- package/dist/modules/workflows/api/tasks/route.js +6 -1
- package/dist/modules/workflows/api/tasks/route.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/[id]/page.js +9 -1
- package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/[id]/page.meta.js +1 -1
- package/dist/modules/workflows/backend/definitions/[id]/page.meta.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/create/page.js +24 -15
- package/dist/modules/workflows/backend/definitions/create/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/create/page.meta.js +1 -1
- package/dist/modules/workflows/backend/definitions/create/page.meta.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js +150 -132
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.meta.js +1 -1
- package/dist/modules/workflows/backend/definitions/visual-editor/page.meta.js.map +2 -2
- package/dist/modules/workflows/backend/events/[id]/page.js +1 -1
- package/dist/modules/workflows/backend/events/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/events/[id]/page.meta.js +2 -2
- package/dist/modules/workflows/backend/events/[id]/page.meta.js.map +2 -2
- package/dist/modules/workflows/backend/instances/[id]/page.meta.js +2 -2
- package/dist/modules/workflows/backend/instances/[id]/page.meta.js.map +2 -2
- package/dist/modules/workflows/backend/tasks/[id]/page.js +1 -1
- package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
- package/dist/modules/workflows/backend/tasks/[id]/page.meta.js +2 -2
- package/dist/modules/workflows/backend/tasks/[id]/page.meta.js.map +2 -2
- package/dist/modules/workflows/backend/tasks/page.js +5 -6
- package/dist/modules/workflows/backend/tasks/page.js.map +2 -2
- package/dist/modules/workflows/cli.js +81 -3
- package/dist/modules/workflows/cli.js.map +3 -3
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js +481 -0
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +7 -0
- package/dist/modules/workflows/components/EventTriggersEditor.js +553 -0
- package/dist/modules/workflows/components/EventTriggersEditor.js.map +7 -0
- package/dist/modules/workflows/data/entities.js +64 -1
- package/dist/modules/workflows/data/entities.js.map +2 -2
- package/dist/modules/workflows/data/validators.js +115 -0
- package/dist/modules/workflows/data/validators.js.map +2 -2
- package/dist/modules/workflows/events.js +38 -0
- package/dist/modules/workflows/events.js.map +7 -0
- package/dist/modules/workflows/examples/checkout-demo-definition.json +1 -5
- package/dist/modules/workflows/examples/order-approval-definition.json +257 -0
- package/dist/modules/workflows/examples/order-approval-guard-rules.json +32 -0
- package/dist/modules/workflows/lib/activity-executor.js +75 -13
- package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
- package/dist/modules/workflows/lib/event-trigger-service.js +308 -0
- package/dist/modules/workflows/lib/event-trigger-service.js.map +7 -0
- package/dist/modules/workflows/lib/graph-utils.js +71 -2
- package/dist/modules/workflows/lib/graph-utils.js.map +2 -2
- package/dist/modules/workflows/lib/seeds.js +22 -5
- package/dist/modules/workflows/lib/seeds.js.map +2 -2
- package/dist/modules/workflows/lib/start-validator.js +33 -23
- package/dist/modules/workflows/lib/start-validator.js.map +2 -2
- package/dist/modules/workflows/lib/transition-handler.js +157 -45
- package/dist/modules/workflows/lib/transition-handler.js.map +3 -3
- package/dist/modules/workflows/migrations/Migration20260123143500.js +36 -0
- package/dist/modules/workflows/migrations/Migration20260123143500.js.map +7 -0
- package/dist/modules/workflows/subscribers/event-trigger.js +78 -0
- package/dist/modules/workflows/subscribers/event-trigger.js.map +7 -0
- package/dist/modules/workflows/widgets/injection/order-approval/widget.client.js +323 -0
- package/dist/modules/workflows/widgets/injection/order-approval/widget.client.js.map +7 -0
- package/dist/modules/workflows/widgets/injection/order-approval/widget.js +17 -0
- package/dist/modules/workflows/widgets/injection/order-approval/widget.js.map +7 -0
- package/dist/modules/workflows/widgets/injection-table.js +19 -0
- package/dist/modules/workflows/widgets/injection-table.js.map +7 -0
- package/generated/entities/api_key/index.ts +1 -0
- package/generated/entities/workflow_event_trigger/index.ts +15 -0
- package/generated/entities.ids.generated.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +2 -2
- package/src/modules/api_keys/data/entities.ts +4 -0
- package/src/modules/api_keys/migrations/.snapshot-open-mercato.json +9 -0
- package/src/modules/api_keys/migrations/Migration20260125204102.ts +13 -0
- package/src/modules/api_keys/services/apiKeyService.ts +85 -0
- package/src/modules/auth/events.ts +39 -0
- package/src/modules/auth/services/rbacService.ts +1 -1
- package/src/modules/business_rules/api/execute/[ruleId]/route.ts +163 -0
- package/src/modules/business_rules/data/validators.ts +40 -0
- package/src/modules/business_rules/index.ts +25 -0
- package/src/modules/business_rules/lib/rule-engine.ts +281 -1
- package/src/modules/catalog/events.ts +45 -0
- package/src/modules/customers/events.ts +63 -0
- package/src/modules/directory/events.ts +31 -0
- package/src/modules/sales/acl.ts +1 -0
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +16 -0
- package/src/modules/sales/commands/documents.ts +74 -1
- package/src/modules/sales/events.ts +82 -0
- package/src/modules/sales/lib/dictionaries.ts +3 -0
- package/src/modules/sales/lib/frontend/documentDataEvents.ts +28 -0
- package/src/modules/workflows/acl.ts +2 -0
- package/src/modules/workflows/api/__tests__/instances.route.test.ts +5 -2
- package/src/modules/workflows/api/instances/route.ts +21 -7
- package/src/modules/workflows/api/tasks/route.ts +7 -1
- package/src/modules/workflows/backend/definitions/[id]/page.meta.ts +1 -1
- package/src/modules/workflows/backend/definitions/[id]/page.tsx +9 -0
- package/src/modules/workflows/backend/definitions/create/page.meta.ts +1 -1
- package/src/modules/workflows/backend/definitions/create/page.tsx +9 -0
- package/src/modules/workflows/backend/definitions/visual-editor/page.meta.ts +1 -1
- package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +21 -3
- package/src/modules/workflows/backend/events/[id]/page.meta.ts +2 -2
- package/src/modules/workflows/backend/events/[id]/page.tsx +1 -1
- package/src/modules/workflows/backend/instances/[id]/page.meta.ts +2 -2
- package/src/modules/workflows/backend/tasks/[id]/page.meta.ts +2 -2
- package/src/modules/workflows/backend/tasks/[id]/page.tsx +1 -1
- package/src/modules/workflows/backend/tasks/page.tsx +5 -6
- package/src/modules/workflows/cli.ts +111 -0
- package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +581 -0
- package/src/modules/workflows/components/EventTriggersEditor.tsx +664 -0
- package/src/modules/workflows/data/entities.ts +124 -0
- package/src/modules/workflows/data/validators.ts +138 -0
- package/src/modules/workflows/events.ts +49 -0
- package/src/modules/workflows/examples/checkout-demo-definition.json +1 -5
- package/src/modules/workflows/examples/order-approval-definition.json +257 -0
- package/src/modules/workflows/examples/order-approval-guard-rules.json +32 -0
- package/src/modules/workflows/i18n/en.json +71 -0
- package/src/modules/workflows/lib/__tests__/activity-executor.test.ts +43 -36
- package/src/modules/workflows/lib/__tests__/transition-handler.test.ts +170 -90
- package/src/modules/workflows/lib/activity-executor.ts +129 -16
- package/src/modules/workflows/lib/event-trigger-service.ts +557 -0
- package/src/modules/workflows/lib/graph-utils.ts +117 -2
- package/src/modules/workflows/lib/seeds.ts +34 -8
- package/src/modules/workflows/lib/start-validator.ts +38 -28
- package/src/modules/workflows/lib/transition-handler.ts +208 -55
- package/src/modules/workflows/migrations/Migration20260123143500.ts +38 -0
- package/src/modules/workflows/subscribers/event-trigger.ts +109 -0
- package/src/modules/workflows/widgets/injection/order-approval/widget.client.tsx +446 -0
- package/src/modules/workflows/widgets/injection/order-approval/widget.ts +16 -0
- package/src/modules/workflows/widgets/injection-table.ts +21 -0
|
@@ -4,9 +4,60 @@ import { hash, compare } from 'bcryptjs'
|
|
|
4
4
|
import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
|
|
5
5
|
import { Role } from '@open-mercato/core/modules/auth/data/entities'
|
|
6
6
|
import { ApiKey } from '../data/entities'
|
|
7
|
+
import { createKmsService } from '@open-mercato/shared/lib/encryption/kms'
|
|
8
|
+
import { encryptWithAesGcm, decryptWithAesGcm } from '@open-mercato/shared/lib/encryption/aes'
|
|
7
9
|
|
|
8
10
|
const BCRYPT_COST = 10
|
|
9
11
|
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// Session Secret Encryption Helpers
|
|
14
|
+
// =============================================================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Encrypt an API key secret for storage.
|
|
18
|
+
* Uses tenant-specific DEK if available, otherwise returns null.
|
|
19
|
+
*/
|
|
20
|
+
async function encryptSessionSecret(
|
|
21
|
+
secret: string,
|
|
22
|
+
tenantId: string | null
|
|
23
|
+
): Promise<string | null> {
|
|
24
|
+
if (!tenantId) return null
|
|
25
|
+
|
|
26
|
+
const kms = createKmsService()
|
|
27
|
+
if (!kms.isHealthy()) return null
|
|
28
|
+
|
|
29
|
+
const dek = await kms.getTenantDek(tenantId)
|
|
30
|
+
if (!dek) {
|
|
31
|
+
// Try to create a DEK if one doesn't exist
|
|
32
|
+
const created = await kms.createTenantDek(tenantId)
|
|
33
|
+
if (!created) return null
|
|
34
|
+
const encrypted = encryptWithAesGcm(secret, created.key)
|
|
35
|
+
return encrypted.value
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const encrypted = encryptWithAesGcm(secret, dek.key)
|
|
39
|
+
return encrypted.value
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Decrypt an API key secret from storage.
|
|
44
|
+
* Returns null if decryption fails or no DEK available.
|
|
45
|
+
*/
|
|
46
|
+
async function decryptSessionSecret(
|
|
47
|
+
encrypted: string,
|
|
48
|
+
tenantId: string | null
|
|
49
|
+
): Promise<string | null> {
|
|
50
|
+
if (!tenantId || !encrypted) return null
|
|
51
|
+
|
|
52
|
+
const kms = createKmsService()
|
|
53
|
+
if (!kms.isHealthy()) return null
|
|
54
|
+
|
|
55
|
+
const dek = await kms.getTenantDek(tenantId)
|
|
56
|
+
if (!dek) return null
|
|
57
|
+
|
|
58
|
+
return decryptWithAesGcm(encrypted, dek.key)
|
|
59
|
+
}
|
|
60
|
+
|
|
10
61
|
export type CreateApiKeyInput = {
|
|
11
62
|
name: string
|
|
12
63
|
description?: string | null
|
|
@@ -117,6 +168,7 @@ export function generateSessionToken(): string {
|
|
|
117
168
|
/**
|
|
118
169
|
* Create an ephemeral API key scoped to a chat session.
|
|
119
170
|
* The key inherits the user's roles and expires after ttlMinutes (default 30).
|
|
171
|
+
* The API key secret is encrypted and stored so it can be recovered for API calls.
|
|
120
172
|
*/
|
|
121
173
|
export async function createSessionApiKey(
|
|
122
174
|
em: EntityManager,
|
|
@@ -127,6 +179,9 @@ export async function createSessionApiKey(
|
|
|
127
179
|
const expiresAt = new Date(Date.now() + ttl * 60 * 1000)
|
|
128
180
|
const keyHash = await hashApiKey(secret)
|
|
129
181
|
|
|
182
|
+
// Encrypt the secret for later retrieval (used by MCP server for API calls)
|
|
183
|
+
const encryptedSecret = await encryptSessionSecret(secret, input.tenantId ?? null)
|
|
184
|
+
|
|
130
185
|
const record = em.create(ApiKey, {
|
|
131
186
|
name: `__session_${input.sessionToken}__`,
|
|
132
187
|
description: 'Ephemeral session API key for AI chat',
|
|
@@ -138,6 +193,7 @@ export async function createSessionApiKey(
|
|
|
138
193
|
createdBy: input.userId,
|
|
139
194
|
sessionToken: input.sessionToken,
|
|
140
195
|
sessionUserId: input.userId,
|
|
196
|
+
sessionSecretEncrypted: encryptedSecret,
|
|
141
197
|
expiresAt,
|
|
142
198
|
createdAt: new Date(),
|
|
143
199
|
})
|
|
@@ -172,6 +228,35 @@ export async function findApiKeyBySessionToken(
|
|
|
172
228
|
return record
|
|
173
229
|
}
|
|
174
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Find a session API key with its decrypted secret.
|
|
233
|
+
* Returns null if not found, expired, deleted, or decryption fails.
|
|
234
|
+
* This is used by the MCP server to recover the API key secret for making
|
|
235
|
+
* authenticated API calls on behalf of the user.
|
|
236
|
+
*/
|
|
237
|
+
export async function findSessionApiKeyWithSecret(
|
|
238
|
+
em: EntityManager,
|
|
239
|
+
sessionToken: string
|
|
240
|
+
): Promise<{ key: ApiKey; secret: string } | null> {
|
|
241
|
+
const record = await findApiKeyBySessionToken(em, sessionToken)
|
|
242
|
+
if (!record) return null
|
|
243
|
+
|
|
244
|
+
// If no encrypted secret stored, cannot recover
|
|
245
|
+
if (!record.sessionSecretEncrypted) {
|
|
246
|
+
console.warn('[ApiKeyService] Session key has no encrypted secret:', sessionToken.slice(0, 12))
|
|
247
|
+
return null
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Decrypt the secret
|
|
251
|
+
const secret = await decryptSessionSecret(record.sessionSecretEncrypted, record.tenantId ?? null)
|
|
252
|
+
if (!secret) {
|
|
253
|
+
console.warn('[ApiKeyService] Failed to decrypt session secret:', sessionToken.slice(0, 12))
|
|
254
|
+
return null
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return { key: record, secret }
|
|
258
|
+
}
|
|
259
|
+
|
|
175
260
|
/**
|
|
176
261
|
* Delete an ephemeral API key by its session token.
|
|
177
262
|
*/
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createModuleEvents } from '@open-mercato/shared/modules/events'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Auth Module Events
|
|
5
|
+
*
|
|
6
|
+
* Declares all events that can be emitted by the auth module.
|
|
7
|
+
*/
|
|
8
|
+
const events = [
|
|
9
|
+
// Users
|
|
10
|
+
{ id: 'auth.users.created', label: 'User Created', entity: 'users', category: 'crud' },
|
|
11
|
+
{ id: 'auth.users.updated', label: 'User Updated', entity: 'users', category: 'crud' },
|
|
12
|
+
{ id: 'auth.users.deleted', label: 'User Deleted', entity: 'users', category: 'crud' },
|
|
13
|
+
|
|
14
|
+
// Roles
|
|
15
|
+
{ id: 'auth.roles.created', label: 'Role Created', entity: 'roles', category: 'crud' },
|
|
16
|
+
{ id: 'auth.roles.updated', label: 'Role Updated', entity: 'roles', category: 'crud' },
|
|
17
|
+
{ id: 'auth.roles.deleted', label: 'Role Deleted', entity: 'roles', category: 'crud' },
|
|
18
|
+
|
|
19
|
+
// Authentication events
|
|
20
|
+
{ id: 'auth.login.success', label: 'Login Successful', category: 'lifecycle' },
|
|
21
|
+
{ id: 'auth.login.failed', label: 'Login Failed', category: 'lifecycle' },
|
|
22
|
+
{ id: 'auth.logout', label: 'User Logged Out', category: 'lifecycle' },
|
|
23
|
+
{ id: 'auth.password.changed', label: 'Password Changed', category: 'lifecycle' },
|
|
24
|
+
{ id: 'auth.password.reset.requested', label: 'Password Reset Requested', category: 'lifecycle' },
|
|
25
|
+
{ id: 'auth.password.reset.completed', label: 'Password Reset Completed', category: 'lifecycle' },
|
|
26
|
+
] as const
|
|
27
|
+
|
|
28
|
+
export const eventsConfig = createModuleEvents({
|
|
29
|
+
moduleId: 'auth',
|
|
30
|
+
events,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
/** Type-safe event emitter for auth module */
|
|
34
|
+
export const emitAuthEvent = eventsConfig.emit
|
|
35
|
+
|
|
36
|
+
/** Event IDs that can be emitted by the auth module */
|
|
37
|
+
export type AuthEventId = typeof events[number]['id']
|
|
38
|
+
|
|
39
|
+
export default eventsConfig
|
|
@@ -69,7 +69,7 @@ export class RbacService {
|
|
|
69
69
|
return granted === required
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
public hasAllFeatures(required: string[], granted: string[]): boolean {
|
|
73
73
|
if (!required.length) return true
|
|
74
74
|
if (!granted.length) return false
|
|
75
75
|
return required.every((req) => granted.some((g) => this.matchFeature(req, g)))
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
7
|
+
import * as ruleEngine from '../../../lib/rule-engine'
|
|
8
|
+
|
|
9
|
+
const executeByIdRequestSchema = z.object({
|
|
10
|
+
data: z.any(),
|
|
11
|
+
dryRun: z.boolean().optional().default(false),
|
|
12
|
+
entityType: z.string().optional(),
|
|
13
|
+
entityId: z.string().optional(),
|
|
14
|
+
eventType: z.string().optional(),
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const executeByIdResponseSchema = z.object({
|
|
18
|
+
success: z.boolean(),
|
|
19
|
+
ruleId: z.string(),
|
|
20
|
+
ruleName: z.string(),
|
|
21
|
+
conditionResult: z.boolean(),
|
|
22
|
+
actionsExecuted: z.object({
|
|
23
|
+
success: z.boolean(),
|
|
24
|
+
results: z.array(z.object({
|
|
25
|
+
type: z.string(),
|
|
26
|
+
success: z.boolean(),
|
|
27
|
+
error: z.string().optional(),
|
|
28
|
+
})),
|
|
29
|
+
}).nullable(),
|
|
30
|
+
executionTime: z.number(),
|
|
31
|
+
error: z.string().optional(),
|
|
32
|
+
logId: z.string().optional(),
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const errorResponseSchema = z.object({
|
|
36
|
+
error: z.string(),
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const routeMetadata = {
|
|
40
|
+
POST: { requireAuth: true, requireFeatures: ['business_rules.execute'] },
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const metadata = routeMetadata
|
|
44
|
+
|
|
45
|
+
interface RouteContext {
|
|
46
|
+
params: Promise<{ ruleId: string }>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function POST(req: Request, context: RouteContext) {
|
|
50
|
+
const auth = await getAuthFromRequest(req)
|
|
51
|
+
if (!auth) {
|
|
52
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const params = await context.params
|
|
56
|
+
const ruleId = params.ruleId
|
|
57
|
+
|
|
58
|
+
if (!ruleId || !z.uuid().safeParse(ruleId).success) {
|
|
59
|
+
return NextResponse.json({ error: 'Invalid rule ID' }, { status: 400 })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const container = await createRequestContainer()
|
|
63
|
+
const em = container.resolve('em') as EntityManager
|
|
64
|
+
|
|
65
|
+
let body: any
|
|
66
|
+
try {
|
|
67
|
+
body = await req.json()
|
|
68
|
+
} catch {
|
|
69
|
+
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const parsed = executeByIdRequestSchema.safeParse(body)
|
|
73
|
+
if (!parsed.success) {
|
|
74
|
+
const errors = parsed.error.issues.map(e => `${e.path.join('.')}: ${e.message}`)
|
|
75
|
+
return NextResponse.json({ error: `Validation failed: ${errors.join(', ')}` }, { status: 400 })
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const { data, dryRun, entityType, entityId, eventType } = parsed.data
|
|
79
|
+
|
|
80
|
+
const execContext: ruleEngine.DirectRuleExecutionContext = {
|
|
81
|
+
ruleId,
|
|
82
|
+
data,
|
|
83
|
+
user: {
|
|
84
|
+
id: auth.sub,
|
|
85
|
+
email: auth.email,
|
|
86
|
+
role: (auth.role as string) ?? undefined,
|
|
87
|
+
},
|
|
88
|
+
tenantId: auth.tenantId ?? '',
|
|
89
|
+
organizationId: auth.orgId ?? '',
|
|
90
|
+
executedBy: auth.sub ?? auth.email ?? undefined,
|
|
91
|
+
dryRun,
|
|
92
|
+
entityType,
|
|
93
|
+
entityId,
|
|
94
|
+
eventType,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const result = await ruleEngine.executeRuleById(em, execContext)
|
|
99
|
+
|
|
100
|
+
const response = {
|
|
101
|
+
success: result.success,
|
|
102
|
+
ruleId: result.ruleId,
|
|
103
|
+
ruleName: result.ruleName,
|
|
104
|
+
conditionResult: result.conditionResult,
|
|
105
|
+
actionsExecuted: result.actionsExecuted ? {
|
|
106
|
+
success: result.actionsExecuted.success,
|
|
107
|
+
results: result.actionsExecuted.results.map(ar => ({
|
|
108
|
+
type: ar.action.type,
|
|
109
|
+
success: ar.success,
|
|
110
|
+
error: ar.error,
|
|
111
|
+
})),
|
|
112
|
+
} : null,
|
|
113
|
+
executionTime: result.executionTime,
|
|
114
|
+
error: result.error,
|
|
115
|
+
logId: result.logId,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Return appropriate status based on result
|
|
119
|
+
const status = result.success ? 200 : (result.error === 'Rule not found' ? 404 : 200)
|
|
120
|
+
return NextResponse.json(response, { status })
|
|
121
|
+
} catch (error) {
|
|
122
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
123
|
+
return NextResponse.json(
|
|
124
|
+
{ error: `Rule execution failed: ${errorMessage}` },
|
|
125
|
+
{ status: 500 }
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export const openApi: OpenApiRouteDoc = {
|
|
131
|
+
tag: 'Business Rules',
|
|
132
|
+
summary: 'Execute a specific business rule by ID',
|
|
133
|
+
methods: {
|
|
134
|
+
POST: {
|
|
135
|
+
summary: 'Execute a specific rule by its database UUID',
|
|
136
|
+
description: 'Directly executes a specific business rule identified by its UUID, bypassing the normal entityType/eventType discovery mechanism. Useful for workflows and targeted rule execution.',
|
|
137
|
+
pathParams: z.object({
|
|
138
|
+
ruleId: z.string().uuid().describe('The database UUID of the business rule to execute'),
|
|
139
|
+
}),
|
|
140
|
+
requestBody: {
|
|
141
|
+
contentType: 'application/json',
|
|
142
|
+
schema: executeByIdRequestSchema,
|
|
143
|
+
},
|
|
144
|
+
responses: [
|
|
145
|
+
{
|
|
146
|
+
status: 200,
|
|
147
|
+
description: 'Rule executed successfully',
|
|
148
|
+
schema: executeByIdResponseSchema,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
status: 404,
|
|
152
|
+
description: 'Rule not found',
|
|
153
|
+
schema: errorResponseSchema,
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
errors: [
|
|
157
|
+
{ status: 400, description: 'Invalid request payload or rule ID', schema: errorResponseSchema },
|
|
158
|
+
{ status: 401, description: 'Unauthorized', schema: errorResponseSchema },
|
|
159
|
+
{ status: 500, description: 'Execution error', schema: errorResponseSchema },
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
}
|
|
@@ -287,3 +287,43 @@ export const ruleDiscoveryOptionsSchema = z.object({
|
|
|
287
287
|
})
|
|
288
288
|
|
|
289
289
|
export type RuleDiscoveryOptionsInput = z.infer<typeof ruleDiscoveryOptionsSchema>
|
|
290
|
+
|
|
291
|
+
// Direct Rule Execution Context Schema (for executing a specific rule by ID)
|
|
292
|
+
export const directRuleExecutionContextSchema = z.object({
|
|
293
|
+
ruleId: z.uuid('ruleId must be a valid UUID'),
|
|
294
|
+
data: z.any(),
|
|
295
|
+
user: z.looseObject({
|
|
296
|
+
id: z.string().optional(),
|
|
297
|
+
email: z.string().optional(),
|
|
298
|
+
role: z.string().optional(),
|
|
299
|
+
}).optional(),
|
|
300
|
+
tenantId: z.uuid('tenantId must be a valid UUID'),
|
|
301
|
+
organizationId: z.uuid('organizationId must be a valid UUID'),
|
|
302
|
+
executedBy: z.string().optional(),
|
|
303
|
+
dryRun: z.boolean().optional(),
|
|
304
|
+
entityType: z.string().optional(),
|
|
305
|
+
entityId: z.string().optional(),
|
|
306
|
+
eventType: z.string().optional(),
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
export type DirectRuleExecutionContextInput = z.infer<typeof directRuleExecutionContextSchema>
|
|
310
|
+
|
|
311
|
+
// Rule ID Execution Context Schema (for executing a specific rule by its string rule_id identifier)
|
|
312
|
+
export const ruleIdExecutionContextSchema = z.object({
|
|
313
|
+
ruleId: z.string().min(1, 'ruleId must be a non-empty string').max(50),
|
|
314
|
+
data: z.any(),
|
|
315
|
+
user: z.looseObject({
|
|
316
|
+
id: z.string().optional(),
|
|
317
|
+
email: z.string().optional(),
|
|
318
|
+
role: z.string().optional(),
|
|
319
|
+
}).optional(),
|
|
320
|
+
tenantId: z.uuid('tenantId must be a valid UUID'),
|
|
321
|
+
organizationId: z.uuid('organizationId must be a valid UUID'),
|
|
322
|
+
executedBy: z.string().optional(),
|
|
323
|
+
dryRun: z.boolean().optional(),
|
|
324
|
+
entityType: z.string().optional(),
|
|
325
|
+
entityId: z.string().optional(),
|
|
326
|
+
eventType: z.string().optional(),
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
export type RuleIdExecutionContextInput = z.infer<typeof ruleIdExecutionContextSchema>
|
|
@@ -8,3 +8,28 @@ export const metadata: ModuleInfo = {
|
|
|
8
8
|
author: 'Patryk Lewczuk',
|
|
9
9
|
license: 'Proprietary',
|
|
10
10
|
}
|
|
11
|
+
|
|
12
|
+
// Export rule engine types and functions for programmatic usage
|
|
13
|
+
export {
|
|
14
|
+
executeRules,
|
|
15
|
+
executeRuleById,
|
|
16
|
+
executeRuleByRuleId,
|
|
17
|
+
executeSingleRule,
|
|
18
|
+
findApplicableRules,
|
|
19
|
+
logRuleExecution,
|
|
20
|
+
type RuleEngineContext,
|
|
21
|
+
type RuleEngineResult,
|
|
22
|
+
type RuleExecutionResult,
|
|
23
|
+
type RuleDiscoveryOptions,
|
|
24
|
+
type DirectRuleExecutionContext,
|
|
25
|
+
type DirectRuleExecutionResult,
|
|
26
|
+
type RuleIdExecutionContext,
|
|
27
|
+
} from './lib/rule-engine'
|
|
28
|
+
|
|
29
|
+
// Export validator schemas
|
|
30
|
+
export {
|
|
31
|
+
directRuleExecutionContextSchema,
|
|
32
|
+
ruleIdExecutionContextSchema,
|
|
33
|
+
type DirectRuleExecutionContextInput,
|
|
34
|
+
type RuleIdExecutionContextInput,
|
|
35
|
+
} from './data/validators'
|