@friggframework/core 2.0.0-next.43 → 2.0.0-next.45
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/database/config.js +29 -1
- package/database/use-cases/test-encryption-use-case.js +6 -5
- package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
- package/handlers/WEBHOOKS.md +653 -0
- package/handlers/backend-utils.js +118 -3
- package/handlers/integration-event-dispatcher.test.js +68 -0
- package/handlers/routers/integration-webhook-routers.js +67 -0
- package/handlers/routers/integration-webhook-routers.test.js +126 -0
- package/handlers/webhook-flow.integration.test.js +356 -0
- package/handlers/workers/integration-defined-workers.test.js +184 -0
- package/index.js +16 -0
- package/integrations/WEBHOOK-QUICKSTART.md +151 -0
- package/integrations/integration-base.js +74 -3
- package/integrations/repositories/process-repository-factory.js +46 -0
- package/integrations/repositories/process-repository-interface.js +90 -0
- package/integrations/repositories/process-repository-mongo.js +190 -0
- package/integrations/repositories/process-repository-postgres.js +217 -0
- package/integrations/tests/doubles/dummy-integration-class.js +1 -8
- package/integrations/use-cases/create-process.js +128 -0
- package/integrations/use-cases/create-process.test.js +178 -0
- package/integrations/use-cases/get-process.js +87 -0
- package/integrations/use-cases/get-process.test.js +190 -0
- package/integrations/use-cases/index.js +8 -0
- package/integrations/use-cases/update-process-metrics.js +201 -0
- package/integrations/use-cases/update-process-metrics.test.js +308 -0
- package/integrations/use-cases/update-process-state.js +119 -0
- package/integrations/use-cases/update-process-state.test.js +256 -0
- package/package.json +5 -5
- package/prisma-mongodb/schema.prisma +44 -0
- package/prisma-postgresql/schema.prisma +45 -0
- package/queues/queuer-util.js +10 -0
- package/user/repositories/user-repository-mongo.js +53 -12
- package/user/repositories/user-repository-postgres.js +53 -14
- package/user/tests/use-cases/login-user.test.js +85 -5
- package/user/tests/user-password-encryption-isolation.test.js +237 -0
- package/user/tests/user-password-hashing.test.js +235 -0
- package/user/use-cases/login-user.js +1 -1
- package/user/user.js +2 -2
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Webhook Quick Start Guide
|
|
2
|
+
|
|
3
|
+
Get webhooks working in your Frigg integration in 3 simple steps.
|
|
4
|
+
|
|
5
|
+
## Step 1: Enable Webhooks
|
|
6
|
+
|
|
7
|
+
Add `webhooks: true` to your Integration Definition:
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
class MyIntegration extends IntegrationBase {
|
|
11
|
+
static Definition = {
|
|
12
|
+
name: 'my-integration',
|
|
13
|
+
version: '1.0.0',
|
|
14
|
+
modules: {
|
|
15
|
+
myapi: { definition: MyApiDefinition },
|
|
16
|
+
},
|
|
17
|
+
webhooks: true, // ← Add this line
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Step 2: Handle Webhook Processing
|
|
23
|
+
|
|
24
|
+
Override the `onWebhook` handler to process webhooks:
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
class MyIntegration extends IntegrationBase {
|
|
28
|
+
// ... Definition ...
|
|
29
|
+
|
|
30
|
+
async onWebhook({ data }) {
|
|
31
|
+
const { body } = data;
|
|
32
|
+
|
|
33
|
+
// You have full access to:
|
|
34
|
+
// - this.myapi (your API modules)
|
|
35
|
+
// - this.config (integration config)
|
|
36
|
+
// - Database operations
|
|
37
|
+
|
|
38
|
+
if (body.event === 'item.created') {
|
|
39
|
+
await this.myapi.api.createItem(body.data);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { processed: true };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Step 3: Deploy
|
|
48
|
+
|
|
49
|
+
Deploy your Frigg app - webhook routes are automatically created:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
POST /api/my-integration-integration/webhooks/:integrationId
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## That's It!
|
|
56
|
+
|
|
57
|
+
The default behavior handles:
|
|
58
|
+
- ✅ Receiving webhooks (instant 200 OK response)
|
|
59
|
+
- ✅ Queuing to SQS
|
|
60
|
+
- ✅ Loading your integration with DB and API modules
|
|
61
|
+
- ✅ Calling your `onWebhook` handler
|
|
62
|
+
|
|
63
|
+
## Optional: Custom Signature Verification
|
|
64
|
+
|
|
65
|
+
Override `onWebhookReceived` for custom signature checks:
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
async onWebhookReceived({ req, res }) {
|
|
69
|
+
// Verify signature
|
|
70
|
+
const signature = req.headers['x-webhook-signature'];
|
|
71
|
+
if (!this.verifySignature(req.body, signature)) {
|
|
72
|
+
return res.status(401).json({ error: 'Invalid signature' });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Queue for processing (default behavior)
|
|
76
|
+
await this.queueWebhook({
|
|
77
|
+
integrationId: req.params.integrationId,
|
|
78
|
+
body: req.body,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
res.status(200).json({ received: true });
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Two Webhook Routes
|
|
86
|
+
|
|
87
|
+
### With Integration ID (Recommended)
|
|
88
|
+
```
|
|
89
|
+
POST /api/{name}-integration/webhooks/:integrationId
|
|
90
|
+
```
|
|
91
|
+
- Full integration loaded in worker
|
|
92
|
+
- Access to DB, config, and API modules
|
|
93
|
+
- Use `this.myapi`, `this.config`, etc.
|
|
94
|
+
|
|
95
|
+
### Without Integration ID
|
|
96
|
+
```
|
|
97
|
+
POST /api/{name}-integration/webhooks
|
|
98
|
+
```
|
|
99
|
+
- Unhydrated integration
|
|
100
|
+
- Useful for system-wide events
|
|
101
|
+
- Limited context
|
|
102
|
+
|
|
103
|
+
## Need Help?
|
|
104
|
+
|
|
105
|
+
See full documentation: `packages/core/handlers/WEBHOOKS.md`
|
|
106
|
+
|
|
107
|
+
## Common Patterns
|
|
108
|
+
|
|
109
|
+
### Slack
|
|
110
|
+
```javascript
|
|
111
|
+
async onWebhookReceived({ req, res }) {
|
|
112
|
+
if (req.body.type === 'url_verification') {
|
|
113
|
+
return res.json({ challenge: req.body.challenge });
|
|
114
|
+
}
|
|
115
|
+
// ... verify signature, queue ...
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Stripe
|
|
120
|
+
```javascript
|
|
121
|
+
async onWebhookReceived({ req, res }) {
|
|
122
|
+
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
|
|
123
|
+
const event = stripe.webhooks.constructEvent(
|
|
124
|
+
JSON.stringify(req.body),
|
|
125
|
+
req.headers['stripe-signature'],
|
|
126
|
+
process.env.STRIPE_WEBHOOK_SECRET
|
|
127
|
+
);
|
|
128
|
+
await this.queueWebhook({ body: event });
|
|
129
|
+
res.status(200).json({ received: true });
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### GitHub
|
|
134
|
+
```javascript
|
|
135
|
+
async onWebhookReceived({ req, res }) {
|
|
136
|
+
const crypto = require('crypto');
|
|
137
|
+
const signature = req.headers['x-hub-signature-256'];
|
|
138
|
+
const hash = crypto
|
|
139
|
+
.createHmac('sha256', process.env.GITHUB_WEBHOOK_SECRET)
|
|
140
|
+
.update(JSON.stringify(req.body))
|
|
141
|
+
.digest('hex');
|
|
142
|
+
|
|
143
|
+
if (`sha256=${hash}` !== signature) {
|
|
144
|
+
return res.status(401).json({ error: 'Invalid signature' });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
await this.queueWebhook({ integrationId: req.params.integrationId, body: req.body });
|
|
148
|
+
res.status(200).json({ received: true });
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
@@ -22,6 +22,8 @@ const constantsToBeMigrated = {
|
|
|
22
22
|
GET_USER_ACTIONS: 'GET_USER_ACTIONS',
|
|
23
23
|
GET_USER_ACTION_OPTIONS: 'GET_USER_ACTION_OPTIONS',
|
|
24
24
|
REFRESH_USER_ACTION_OPTIONS: 'REFRESH_USER_ACTION_OPTIONS',
|
|
25
|
+
WEBHOOK_RECEIVED: 'WEBHOOK_RECEIVED', // HTTP handler, no DB
|
|
26
|
+
ON_WEBHOOK: 'ON_WEBHOOK', // Queue worker, DB-connected
|
|
25
27
|
// etc...
|
|
26
28
|
},
|
|
27
29
|
types: {
|
|
@@ -130,6 +132,14 @@ class IntegrationBase {
|
|
|
130
132
|
type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
|
|
131
133
|
handler: this.refreshActionOptions,
|
|
132
134
|
},
|
|
135
|
+
[constantsToBeMigrated.defaultEvents.WEBHOOK_RECEIVED]: {
|
|
136
|
+
type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
|
|
137
|
+
handler: this.onWebhookReceived,
|
|
138
|
+
},
|
|
139
|
+
[constantsToBeMigrated.defaultEvents.ON_WEBHOOK]: {
|
|
140
|
+
type: constantsToBeMigrated.types.LIFE_CYCLE_EVENT,
|
|
141
|
+
handler: this.onWebhook,
|
|
142
|
+
},
|
|
133
143
|
};
|
|
134
144
|
}
|
|
135
145
|
|
|
@@ -294,7 +304,9 @@ class IntegrationBase {
|
|
|
294
304
|
await this.updateIntegrationStatus.execute(integrationId, 'ENABLED');
|
|
295
305
|
}
|
|
296
306
|
|
|
297
|
-
async onUpdate(params) {
|
|
307
|
+
async onUpdate(params) {
|
|
308
|
+
return this.validateConfig();
|
|
309
|
+
}
|
|
298
310
|
|
|
299
311
|
async onDelete(params) {}
|
|
300
312
|
|
|
@@ -357,6 +369,50 @@ class IntegrationBase {
|
|
|
357
369
|
return options;
|
|
358
370
|
}
|
|
359
371
|
|
|
372
|
+
/**
|
|
373
|
+
* WEBHOOK EVENT HANDLERS
|
|
374
|
+
*/
|
|
375
|
+
async onWebhookReceived({ req, res }) {
|
|
376
|
+
// Default: queue webhook for processing
|
|
377
|
+
const body = req.body;
|
|
378
|
+
const integrationId = req.params.integrationId || null;
|
|
379
|
+
|
|
380
|
+
await this.queueWebhook({
|
|
381
|
+
integrationId,
|
|
382
|
+
body,
|
|
383
|
+
headers: req.headers,
|
|
384
|
+
query: req.query,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
res.status(200).json({ received: true });
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async onWebhook({ data }) {
|
|
391
|
+
// Default: no-op, integrations override this
|
|
392
|
+
console.log('Webhook received:', data);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async queueWebhook(data) {
|
|
396
|
+
const { QueuerUtil } = require('../queues');
|
|
397
|
+
|
|
398
|
+
const queueName = `${this.constructor.Definition.name
|
|
399
|
+
.toUpperCase()
|
|
400
|
+
.replace(/-/g, '_')}_QUEUE_URL`;
|
|
401
|
+
const queueUrl = process.env[queueName];
|
|
402
|
+
|
|
403
|
+
if (!queueUrl) {
|
|
404
|
+
throw new Error(`Queue URL not found for ${queueName}`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return QueuerUtil.send(
|
|
408
|
+
{
|
|
409
|
+
event: 'ON_WEBHOOK',
|
|
410
|
+
data,
|
|
411
|
+
},
|
|
412
|
+
queueUrl
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
360
416
|
// === Domain Methods (moved from Integration.js) ===
|
|
361
417
|
|
|
362
418
|
getConfig() {
|
|
@@ -403,8 +459,14 @@ class IntegrationBase {
|
|
|
403
459
|
return this.userId.toString() === userId.toString();
|
|
404
460
|
}
|
|
405
461
|
|
|
462
|
+
registerEventHandlers() {
|
|
463
|
+
this.on = {
|
|
464
|
+
...this.defaultEvents,
|
|
465
|
+
...this.events,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
406
469
|
async initialize() {
|
|
407
|
-
// Load dynamic user actions
|
|
408
470
|
try {
|
|
409
471
|
const additionalUserActions = await this.loadDynamicUserActions();
|
|
410
472
|
this.events = { ...this.events, ...additionalUserActions };
|
|
@@ -412,7 +474,16 @@ class IntegrationBase {
|
|
|
412
474
|
this.addError(e);
|
|
413
475
|
}
|
|
414
476
|
|
|
415
|
-
|
|
477
|
+
this.registerEventHandlers();
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async send(event, object) {
|
|
481
|
+
if (!this.on[event]) {
|
|
482
|
+
throw new Error(
|
|
483
|
+
`Event ${event} is not defined in the Integration event object`
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
return this.on[event].handler.call(this, object);
|
|
416
487
|
}
|
|
417
488
|
|
|
418
489
|
getOptionDetails() {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const { ProcessRepositoryMongo } = require('./process-repository-mongo');
|
|
2
|
+
const { ProcessRepositoryPostgres } = require('./process-repository-postgres');
|
|
3
|
+
const config = require('../../database/config');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Process Repository Factory
|
|
7
|
+
* Creates the appropriate repository adapter based on database type
|
|
8
|
+
*
|
|
9
|
+
* This implements the Factory pattern for Hexagonal Architecture:
|
|
10
|
+
* - Reads database type from app definition (backend/index.js)
|
|
11
|
+
* - Returns correct adapter (MongoDB or PostgreSQL)
|
|
12
|
+
* - Provides clear error for unsupported databases
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* ```javascript
|
|
16
|
+
* const repository = createProcessRepository();
|
|
17
|
+
* await repository.create({ userId, integrationId, name, type, state });
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @returns {ProcessRepositoryInterface} Configured repository adapter
|
|
21
|
+
* @throws {Error} If database type is not supported
|
|
22
|
+
*/
|
|
23
|
+
function createProcessRepository() {
|
|
24
|
+
const dbType = config.DB_TYPE;
|
|
25
|
+
|
|
26
|
+
switch (dbType) {
|
|
27
|
+
case 'mongodb':
|
|
28
|
+
return new ProcessRepositoryMongo();
|
|
29
|
+
|
|
30
|
+
case 'postgresql':
|
|
31
|
+
return new ProcessRepositoryPostgres();
|
|
32
|
+
|
|
33
|
+
default:
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Unsupported database type: ${dbType}. Supported values: 'mongodb', 'postgresql'`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
createProcessRepository,
|
|
42
|
+
// Export adapters for direct testing
|
|
43
|
+
ProcessRepositoryMongo,
|
|
44
|
+
ProcessRepositoryPostgres,
|
|
45
|
+
};
|
|
46
|
+
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProcessRepository Interface
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract for Process data access operations.
|
|
5
|
+
* Implementations must provide concrete methods for all operations.
|
|
6
|
+
*
|
|
7
|
+
* This interface supports the Hexagonal Architecture pattern by:
|
|
8
|
+
* - Defining clear boundaries between domain logic and data access
|
|
9
|
+
* - Allowing multiple implementations (MongoDB, PostgreSQL, in-memory)
|
|
10
|
+
* - Enabling dependency injection and testability
|
|
11
|
+
*/
|
|
12
|
+
class ProcessRepositoryInterface {
|
|
13
|
+
/**
|
|
14
|
+
* Create a new process record
|
|
15
|
+
* @param {Object} processData - Process data to create
|
|
16
|
+
* @param {string} processData.userId - User ID
|
|
17
|
+
* @param {string} processData.integrationId - Integration ID
|
|
18
|
+
* @param {string} processData.name - Process name
|
|
19
|
+
* @param {string} processData.type - Process type
|
|
20
|
+
* @param {string} processData.state - Initial state
|
|
21
|
+
* @param {Object} [processData.context] - Process context
|
|
22
|
+
* @param {Object} [processData.results] - Process results
|
|
23
|
+
* @param {string[]} [processData.childProcesses] - Child process IDs
|
|
24
|
+
* @param {string} [processData.parentProcessId] - Parent process ID
|
|
25
|
+
* @returns {Promise<Object>} Created process record
|
|
26
|
+
*/
|
|
27
|
+
async create(processData) {
|
|
28
|
+
throw new Error('Method create() must be implemented');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Find a process by ID
|
|
33
|
+
* @param {string} processId - Process ID to find
|
|
34
|
+
* @returns {Promise<Object|null>} Process record or null if not found
|
|
35
|
+
*/
|
|
36
|
+
async findById(processId) {
|
|
37
|
+
throw new Error('Method findById() must be implemented');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Update a process record
|
|
42
|
+
* @param {string} processId - Process ID to update
|
|
43
|
+
* @param {Object} updates - Fields to update
|
|
44
|
+
* @returns {Promise<Object>} Updated process record
|
|
45
|
+
*/
|
|
46
|
+
async update(processId, updates) {
|
|
47
|
+
throw new Error('Method update() must be implemented');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Find processes by integration and type
|
|
52
|
+
* @param {string} integrationId - Integration ID
|
|
53
|
+
* @param {string} type - Process type
|
|
54
|
+
* @returns {Promise<Array>} Array of process records
|
|
55
|
+
*/
|
|
56
|
+
async findByIntegrationAndType(integrationId, type) {
|
|
57
|
+
throw new Error('Method findByIntegrationAndType() must be implemented');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Find active processes (not in excluded states)
|
|
62
|
+
* @param {string} integrationId - Integration ID
|
|
63
|
+
* @param {string[]} [excludeStates=['COMPLETED', 'ERROR']] - States to exclude
|
|
64
|
+
* @returns {Promise<Array>} Array of active process records
|
|
65
|
+
*/
|
|
66
|
+
async findActiveProcesses(integrationId, excludeStates = ['COMPLETED', 'ERROR']) {
|
|
67
|
+
throw new Error('Method findActiveProcesses() must be implemented');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Find a process by name (most recent)
|
|
72
|
+
* @param {string} name - Process name
|
|
73
|
+
* @returns {Promise<Object|null>} Most recent process with given name, or null
|
|
74
|
+
*/
|
|
75
|
+
async findByName(name) {
|
|
76
|
+
throw new Error('Method findByName() must be implemented');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Delete a process by ID
|
|
81
|
+
* @param {string} processId - Process ID to delete
|
|
82
|
+
* @returns {Promise<void>}
|
|
83
|
+
*/
|
|
84
|
+
async deleteById(processId) {
|
|
85
|
+
throw new Error('Method deleteById() must be implemented');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { ProcessRepositoryInterface };
|
|
90
|
+
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
const { prisma } = require('../../database/prisma');
|
|
2
|
+
const { ProcessRepositoryInterface } = require('./process-repository-interface');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* MongoDB Process Repository Adapter
|
|
6
|
+
* Handles process persistence using Prisma with MongoDB
|
|
7
|
+
*
|
|
8
|
+
* MongoDB-specific characteristics:
|
|
9
|
+
* - Uses scalar fields for relations (userId, integrationId)
|
|
10
|
+
* - IDs are strings with @db.ObjectId
|
|
11
|
+
* - JSON fields for flexible context and results storage
|
|
12
|
+
* - Array field for childProcesses references
|
|
13
|
+
*
|
|
14
|
+
* Design Philosophy:
|
|
15
|
+
* - Generic Process model supports any type of long-running operation
|
|
16
|
+
* - Context and results stored as JSON for maximum flexibility
|
|
17
|
+
* - Integration-specific logic lives in use cases and services
|
|
18
|
+
*/
|
|
19
|
+
class ProcessRepositoryMongo extends ProcessRepositoryInterface {
|
|
20
|
+
constructor() {
|
|
21
|
+
super();
|
|
22
|
+
this.prisma = prisma;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create a new process record
|
|
27
|
+
* @param {Object} processData - Process data to create
|
|
28
|
+
* @returns {Promise<Object>} Created process record
|
|
29
|
+
*/
|
|
30
|
+
async create(processData) {
|
|
31
|
+
const process = await this.prisma.process.create({
|
|
32
|
+
data: {
|
|
33
|
+
userId: processData.userId,
|
|
34
|
+
integrationId: processData.integrationId,
|
|
35
|
+
name: processData.name,
|
|
36
|
+
type: processData.type,
|
|
37
|
+
state: processData.state || 'INITIALIZING',
|
|
38
|
+
context: processData.context || {},
|
|
39
|
+
results: processData.results || {},
|
|
40
|
+
childProcesses: processData.childProcesses || [],
|
|
41
|
+
parentProcessId: processData.parentProcessId || null,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return this._toPlainObject(process);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Find a process by ID
|
|
50
|
+
* @param {string} processId - Process ID to find
|
|
51
|
+
* @returns {Promise<Object|null>} Process record or null if not found
|
|
52
|
+
*/
|
|
53
|
+
async findById(processId) {
|
|
54
|
+
const process = await this.prisma.process.findUnique({
|
|
55
|
+
where: { id: processId },
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return process ? this._toPlainObject(process) : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Update a process record
|
|
63
|
+
* @param {string} processId - Process ID to update
|
|
64
|
+
* @param {Object} updates - Fields to update
|
|
65
|
+
* @returns {Promise<Object>} Updated process record
|
|
66
|
+
*/
|
|
67
|
+
async update(processId, updates) {
|
|
68
|
+
// Prepare update data, excluding undefined values
|
|
69
|
+
const updateData = {};
|
|
70
|
+
|
|
71
|
+
if (updates.state !== undefined) {
|
|
72
|
+
updateData.state = updates.state;
|
|
73
|
+
}
|
|
74
|
+
if (updates.context !== undefined) {
|
|
75
|
+
updateData.context = updates.context;
|
|
76
|
+
}
|
|
77
|
+
if (updates.results !== undefined) {
|
|
78
|
+
updateData.results = updates.results;
|
|
79
|
+
}
|
|
80
|
+
if (updates.childProcesses !== undefined) {
|
|
81
|
+
updateData.childProcesses = updates.childProcesses;
|
|
82
|
+
}
|
|
83
|
+
if (updates.parentProcessId !== undefined) {
|
|
84
|
+
updateData.parentProcessId = updates.parentProcessId;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const process = await this.prisma.process.update({
|
|
88
|
+
where: { id: processId },
|
|
89
|
+
data: updateData,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return this._toPlainObject(process);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Find processes by integration and type
|
|
97
|
+
* @param {string} integrationId - Integration ID
|
|
98
|
+
* @param {string} type - Process type
|
|
99
|
+
* @returns {Promise<Array>} Array of process records
|
|
100
|
+
*/
|
|
101
|
+
async findByIntegrationAndType(integrationId, type) {
|
|
102
|
+
const processes = await this.prisma.process.findMany({
|
|
103
|
+
where: {
|
|
104
|
+
integrationId,
|
|
105
|
+
type,
|
|
106
|
+
},
|
|
107
|
+
orderBy: {
|
|
108
|
+
createdAt: 'desc',
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return processes.map((p) => this._toPlainObject(p));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Find active processes (not in excluded states)
|
|
117
|
+
* @param {string} integrationId - Integration ID
|
|
118
|
+
* @param {string[]} [excludeStates=['COMPLETED', 'ERROR']] - States to exclude
|
|
119
|
+
* @returns {Promise<Array>} Array of active process records
|
|
120
|
+
*/
|
|
121
|
+
async findActiveProcesses(integrationId, excludeStates = ['COMPLETED', 'ERROR']) {
|
|
122
|
+
const processes = await this.prisma.process.findMany({
|
|
123
|
+
where: {
|
|
124
|
+
integrationId,
|
|
125
|
+
state: {
|
|
126
|
+
notIn: excludeStates,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
orderBy: {
|
|
130
|
+
createdAt: 'desc',
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return processes.map((p) => this._toPlainObject(p));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Find a process by name (most recent)
|
|
139
|
+
* @param {string} name - Process name
|
|
140
|
+
* @returns {Promise<Object|null>} Most recent process with given name, or null
|
|
141
|
+
*/
|
|
142
|
+
async findByName(name) {
|
|
143
|
+
const process = await this.prisma.process.findFirst({
|
|
144
|
+
where: { name },
|
|
145
|
+
orderBy: {
|
|
146
|
+
createdAt: 'desc',
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return process ? this._toPlainObject(process) : null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Delete a process by ID
|
|
155
|
+
* @param {string} processId - Process ID to delete
|
|
156
|
+
* @returns {Promise<void>}
|
|
157
|
+
*/
|
|
158
|
+
async deleteById(processId) {
|
|
159
|
+
await this.prisma.process.delete({
|
|
160
|
+
where: { id: processId },
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Convert Prisma model to plain JavaScript object
|
|
166
|
+
* Ensures consistent API across repository implementations
|
|
167
|
+
* @private
|
|
168
|
+
* @param {Object} process - Prisma process model
|
|
169
|
+
* @returns {Object} Plain process object
|
|
170
|
+
*/
|
|
171
|
+
_toPlainObject(process) {
|
|
172
|
+
return {
|
|
173
|
+
id: process.id,
|
|
174
|
+
userId: process.userId,
|
|
175
|
+
integrationId: process.integrationId,
|
|
176
|
+
name: process.name,
|
|
177
|
+
type: process.type,
|
|
178
|
+
state: process.state,
|
|
179
|
+
context: process.context,
|
|
180
|
+
results: process.results,
|
|
181
|
+
childProcesses: process.childProcesses,
|
|
182
|
+
parentProcessId: process.parentProcessId,
|
|
183
|
+
createdAt: process.createdAt,
|
|
184
|
+
updatedAt: process.updatedAt,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = { ProcessRepositoryMongo };
|
|
190
|
+
|