@friggframework/core 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.dba001a.0
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/CHANGELOG.md +66 -67
- package/CLAUDE.md +148 -124
- package/README.md +165 -130
- package/admin-scripts/index.js +52 -0
- package/admin-scripts/repositories/admin-api-key-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/admin-api-key-repository-factory.js +51 -0
- package/admin-scripts/repositories/admin-api-key-repository-interface.js +121 -0
- package/admin-scripts/repositories/admin-api-key-repository-mongo.js +155 -0
- package/admin-scripts/repositories/admin-api-key-repository-postgres.js +189 -0
- package/admin-scripts/repositories/admin-process-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/admin-process-repository-factory.js +49 -0
- package/admin-scripts/repositories/admin-process-repository-interface.js +150 -0
- package/admin-scripts/repositories/admin-process-repository-mongo.js +213 -0
- package/admin-scripts/repositories/admin-process-repository-postgres.js +251 -0
- package/admin-scripts/repositories/script-execution-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/script-execution-repository-factory.js +51 -0
- package/admin-scripts/repositories/script-execution-repository-interface.js +193 -0
- package/admin-scripts/repositories/script-execution-repository-mongo.js +278 -0
- package/admin-scripts/repositories/script-execution-repository-postgres.js +320 -0
- package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/script-schedule-repository-factory.js +49 -0
- package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
- package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
- package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
- package/application/commands/admin-script-commands.js +348 -0
- package/application/commands/integration-commands.js +12 -6
- package/application/index.js +5 -3
- package/assertions/get.js +17 -7
- package/associations/association.js +59 -57
- package/core/CLAUDE.md +222 -156
- package/core/Worker.js +33 -12
- package/credential/credential-router.js +262 -0
- package/credential/repositories/credential-repository-documentdb.js +71 -1
- package/credential/repositories/credential-repository-factory.js +4 -6
- package/credential/repositories/credential-repository-interface.js +8 -2
- package/credential/repositories/credential-repository-mongo.js +45 -4
- package/credential/repositories/credential-repository-postgres.js +44 -3
- package/credential/repositories/credential-repository.js +3 -1
- package/credential/use-cases/delete-credential-for-user.js +50 -0
- package/credential/use-cases/get-credential-for-user.js +21 -3
- package/credential/use-cases/list-credentials-for-user.js +31 -0
- package/credential/use-cases/reauthorize-credential.js +103 -0
- package/credential/use-cases/update-authentication-status.js +5 -2
- package/database/MONGODB_TRANSACTION_FIX.md +72 -52
- package/database/config.js +13 -59
- package/database/documentdb-encryption-service.js +29 -13
- package/database/documentdb-utils.js +21 -12
- package/database/encryption/README.md +53 -36
- package/database/encryption/encryption-schema-registry.js +29 -23
- package/database/encryption/field-encryption-service.js +15 -4
- package/database/encryption/logger.js +3 -2
- package/database/encryption/prisma-encryption-extension.js +5 -8
- package/database/prisma.js +22 -14
- package/database/repositories/health-check-repository-factory.js +7 -5
- package/database/repositories/health-check-repository-interface.js +15 -5
- package/database/repositories/health-check-repository-postgres.js +1 -1
- package/database/use-cases/check-database-health-use-case.js +3 -2
- package/database/use-cases/check-database-state-use-case.js +8 -7
- package/database/use-cases/get-database-state-via-worker-use-case.js +15 -12
- package/database/use-cases/get-migration-status-use-case.js +4 -2
- package/database/use-cases/run-database-migration-use-case.js +41 -9
- package/database/use-cases/test-encryption-use-case.js +4 -5
- package/database/use-cases/trigger-database-migration-use-case.js +9 -8
- package/database/utils/mongodb-collection-utils.js +11 -3
- package/database/utils/mongodb-schema-init.js +10 -4
- package/database/utils/prisma-runner.js +144 -74
- package/database/utils/prisma-schema-parser.js +2 -2
- package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +135 -80
- package/encrypt/Cryptor.js +54 -49
- package/encrypt/aes-encryption-key-provider.js +82 -0
- package/encrypt/encryption-key-provider-interface.js +50 -0
- package/errors/fetch-error.js +1 -1
- package/generated/prisma-mongodb/edge.js +59 -4
- package/generated/prisma-mongodb/index-browser.js +49 -1
- package/generated/prisma-mongodb/index.d.ts +4023 -61
- package/generated/prisma-mongodb/index.js +59 -4
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +87 -0
- package/generated/prisma-mongodb/wasm.js +59 -4
- package/generated/prisma-postgresql/edge.js +58 -4
- package/generated/prisma-postgresql/index-browser.js +48 -1
- package/generated/prisma-postgresql/index.d.ts +4738 -54
- package/generated/prisma-postgresql/index.js +58 -4
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +85 -0
- package/generated/prisma-postgresql/wasm.js +58 -4
- package/handlers/WEBHOOKS.md +104 -81
- package/handlers/app-definition-loader.js +53 -10
- package/handlers/app-handler-helpers.js +14 -3
- package/handlers/backend-utils.js +0 -1
- package/handlers/database-migration-handler.js +64 -45
- package/handlers/middleware/admin-auth.js +53 -0
- package/handlers/routers/HEALTHCHECK.md +170 -135
- package/handlers/routers/admin.js +450 -0
- package/handlers/routers/auth.js +23 -2
- package/handlers/routers/db-migration.handler.js +2 -3
- package/handlers/routers/db-migration.js +102 -66
- package/handlers/routers/docs.js +180 -0
- package/handlers/routers/health.js +39 -249
- package/handlers/routers/integration-defined-routers.js +4 -4
- package/handlers/routers/integration-webhook-routers.js +17 -7
- package/handlers/routers/middleware/loadUser.js +39 -0
- package/handlers/routers/middleware/requireAdmin.js +43 -0
- package/handlers/routers/middleware/requireLoggedInUser.js +19 -0
- package/handlers/routers/websocket.js +9 -3
- package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
- package/handlers/workers/db-migration.js +74 -35
- package/index.js +50 -16
- package/infrastructure/scheduler/index.js +15 -11
- package/infrastructure/scheduler/netlify-scheduler-adapter.js +185 -0
- package/infrastructure/scheduler/scheduler-service-factory.js +38 -31
- package/integrations/WEBHOOK-QUICKSTART.md +21 -14
- package/integrations/index.js +4 -0
- package/integrations/integration-base.js +9 -6
- package/integrations/integration-router.js +1828 -157
- package/integrations/options.js +47 -20
- package/integrations/repositories/integration-mapping-repository-factory.js +4 -8
- package/integrations/repositories/integration-repository-documentdb.js +53 -24
- package/integrations/repositories/integration-repository-factory.js +6 -6
- package/integrations/repositories/integration-repository-interface.js +27 -9
- package/integrations/repositories/integration-repository-postgres.js +3 -3
- package/integrations/repositories/process-repository-factory.js +4 -7
- package/integrations/repositories/process-repository-interface.js +9 -5
- package/integrations/repositories/process-repository-mongo.js +7 -3
- package/integrations/repositories/process-repository-postgres.js +2 -2
- package/integrations/tests/doubles/config-capturing-integration.js +9 -8
- package/integrations/tests/doubles/dummy-integration-class.js +81 -8
- package/integrations/tests/doubles/test-integration-repository.js +51 -11
- package/integrations/use-cases/create-integration.js +36 -32
- package/integrations/use-cases/create-process.js +20 -9
- package/integrations/use-cases/delete-integration-for-user.js +5 -4
- package/integrations/use-cases/execute-proxy-request.js +564 -0
- package/integrations/use-cases/get-integration-for-user.js +25 -10
- package/integrations/use-cases/get-integration-instance-by-definition.js +13 -7
- package/integrations/use-cases/get-integrations-for-user.js +4 -1
- package/integrations/use-cases/get-possible-integrations.js +1 -1
- package/integrations/use-cases/get-process.js +5 -6
- package/integrations/use-cases/index.js +3 -1
- package/integrations/use-cases/load-integration-context.js +1 -5
- package/integrations/use-cases/update-process-state.js +10 -8
- package/integrations/utils/map-integration-dto.js +9 -3
- package/jest-setup.js +1 -1
- package/jest-teardown.js +1 -1
- package/lambda/TimeoutCatcher.js +32 -30
- package/lambda/index.js +1 -1
- package/logs/index.js +3 -3
- package/modules/domain/entities/AuthorizationSession.js +131 -0
- package/modules/domain/entities/index.js +10 -0
- package/modules/module.js +4 -9
- package/modules/repositories/authorization-session-repository-factory.js +53 -0
- package/modules/repositories/authorization-session-repository-interface.js +75 -0
- package/modules/repositories/authorization-session-repository-mongo.js +169 -0
- package/modules/repositories/authorization-session-repository-postgres.js +170 -0
- package/modules/repositories/module-repository-documentdb.js +89 -41
- package/modules/repositories/module-repository-factory.js +4 -6
- package/modules/repositories/module-repository-interface.js +13 -0
- package/modules/repositories/module-repository-mongo.js +40 -5
- package/modules/repositories/module-repository-postgres.js +42 -5
- package/modules/repositories/module-repository.js +28 -0
- package/modules/requester/api-key.js +0 -2
- package/modules/requester/basic.js +0 -1
- package/modules/requester/oauth-2.js +3 -14
- package/modules/test/mock-api/definition.js +15 -10
- package/modules/test/mock-api/mocks/hubspot.js +36 -36
- package/modules/tests/doubles/test-module-factory.js +10 -4
- package/modules/tests/doubles/test-module-repository.js +1 -1
- package/modules/use-cases/delete-module-entity.js +23 -0
- package/modules/use-cases/get-authorization-requirements.js +110 -0
- package/modules/use-cases/get-entities-for-user.js +1 -1
- package/modules/use-cases/get-entity-options-by-id.js +8 -6
- package/modules/use-cases/get-module-entity-by-id.js +16 -0
- package/modules/use-cases/get-module.js +12 -9
- package/modules/use-cases/process-authorization-callback.js +12 -6
- package/modules/use-cases/process-authorization-step.js +160 -0
- package/modules/use-cases/refresh-entity-options.js +8 -6
- package/modules/use-cases/start-authorization-session.js +86 -0
- package/modules/use-cases/test-module-auth.js +8 -6
- package/modules/use-cases/update-module-entity.js +27 -0
- package/modules/utils/map-module-dto.js +2 -2
- package/openapi/openapi-spec-generator.js +370 -0
- package/openapi/openapi-v1.yaml +1360 -0
- package/openapi/openapi-v2.yaml +1535 -0
- package/package.json +7 -9
- package/prisma-mongodb/schema.prisma +87 -0
- package/prisma-postgresql/migrations/20260222000000_add_admin_process_and_script_schedule/migration.sql +55 -0
- package/prisma-postgresql/schema.prisma +85 -0
- package/providers/index.js +13 -0
- package/providers/resolve-provider.js +93 -0
- package/queues/index.js +19 -0
- package/queues/providers/index.js +7 -0
- package/queues/providers/netlify-background-provider.js +76 -0
- package/queues/providers/qstash-queue-provider.js +122 -0
- package/queues/queue-client-interface.js +55 -0
- package/queues/queue-provider-factory.js +93 -0
- package/queues/queue-provider.js +46 -0
- package/queues/queuer-util.js +33 -25
- package/syncs/repositories/sync-repository-documentdb.js +39 -19
- package/syncs/repositories/sync-repository-factory.js +4 -4
- package/syncs/repositories/sync-repository-interface.js +6 -2
- package/syncs/repositories/sync-repository-postgres.js +19 -13
- package/syncs/sync.js +104 -100
- package/token/repositories/token-repository-factory.js +4 -6
- package/types/assertions/index.d.ts +72 -72
- package/types/core/index.d.ts +98 -53
- package/types/errors/index.d.ts +61 -54
- package/types/eslint-config/index.d.ts +40 -40
- package/types/lambda/index.d.ts +26 -26
- package/types/logs/index.d.ts +4 -4
- package/types/prettier-config/index.d.ts +5 -5
- package/types/test-environment/index.d.ts +14 -14
- package/types/tsconfig.json +95 -93
- package/user/repositories/user-repository-factory.js +4 -4
- package/user/tests/doubles/test-user-repository.js +5 -2
- package/user/use-cases/authenticate-user.js +2 -4
- package/user/use-cases/authenticate-with-shared-secret.js +1 -1
- package/user/use-cases/create-individual-user.js +8 -7
- package/user/use-cases/create-organization-user.js +1 -1
- package/user/use-cases/create-token-for-user-id.js +1 -1
- package/user/use-cases/delete-user.js +47 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +0 -2
- package/user/use-cases/get-user-from-bearer-token.js +7 -3
- package/user/use-cases/get-user-from-x-frigg-headers.js +14 -11
- package/user/use-cases/login-user.js +8 -8
- package/user/user.js +18 -4
- package/utils/backend-path.js +2 -2
- package/utils/index.js +4 -1
- package/websocket/repositories/websocket-connection-repository-documentdb.js +46 -21
- package/websocket/repositories/websocket-connection-repository-factory.js +4 -8
- package/websocket/repositories/websocket-connection-repository-mongo.js +26 -16
- package/websocket/repositories/websocket-connection-repository-postgres.js +26 -15
- package/websocket/repositories/websocket-connection-repository.js +26 -20
- package/websocket/websocket-message-sender-interface.js +38 -0
- package/database/adapters/lambda-invoker.js +0 -97
- package/database/repositories/migration-status-repository-s3.js +0 -137
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +0 -184
package/README.md
CHANGED
|
@@ -4,16 +4,16 @@ The `@friggframework/core` package is the foundational layer of the Frigg Framew
|
|
|
4
4
|
|
|
5
5
|
## Table of Contents
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
7
|
+
- [Architecture Overview](#architecture-overview)
|
|
8
|
+
- [Installation](#installation)
|
|
9
|
+
- [Quick Start](#quick-start)
|
|
10
|
+
- [Core Components](#core-components)
|
|
11
|
+
- [Hexagonal Architecture](#hexagonal-architecture)
|
|
12
|
+
- [Usage Examples](#usage-examples)
|
|
13
|
+
- [Testing](#testing)
|
|
14
|
+
- [Development](#development)
|
|
15
|
+
- [API Reference](#api-reference)
|
|
16
|
+
- [Contributing](#contributing)
|
|
17
17
|
|
|
18
18
|
## Architecture Overview
|
|
19
19
|
|
|
@@ -67,11 +67,13 @@ yarn add @friggframework/core
|
|
|
67
67
|
`@friggframework/core` supports both MongoDB and PostgreSQL via Prisma ORM. **Prisma is an optional peer dependency** - you only need to install it if you're using database features that require migrations or schema generation.
|
|
68
68
|
|
|
69
69
|
**When you need Prisma:**
|
|
70
|
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
70
|
+
|
|
71
|
+
- Running database migrations (`prisma migrate`, `prisma db push`)
|
|
72
|
+
- Generating Prisma clients for your application
|
|
73
|
+
- Using the migration Lambda function (`dbMigrate`)
|
|
73
74
|
|
|
74
75
|
**Installation:**
|
|
76
|
+
|
|
75
77
|
```bash
|
|
76
78
|
# Install Prisma CLI and Client as dev dependencies
|
|
77
79
|
npm install --save-dev prisma @prisma/client
|
|
@@ -81,6 +83,7 @@ yarn add -D prisma @prisma/client
|
|
|
81
83
|
```
|
|
82
84
|
|
|
83
85
|
**Generate Prisma Clients:**
|
|
86
|
+
|
|
84
87
|
```bash
|
|
85
88
|
# From @friggframework/core directory
|
|
86
89
|
npm run prisma:generate:mongo # MongoDB only
|
|
@@ -92,9 +95,9 @@ npm run prisma:generate # Both databases
|
|
|
92
95
|
|
|
93
96
|
### Prerequisites
|
|
94
97
|
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
+
- Node.js 16+
|
|
99
|
+
- MongoDB 4.4+ (for data persistence)
|
|
100
|
+
- AWS credentials (for SQS, KMS, Lambda deployment)
|
|
98
101
|
|
|
99
102
|
### Environment Variables
|
|
100
103
|
|
|
@@ -120,11 +123,13 @@ LOG_LEVEL=info
|
|
|
120
123
|
The heart of the framework - manages integration lifecycle and business logic.
|
|
121
124
|
|
|
122
125
|
**Key Classes:**
|
|
123
|
-
|
|
124
|
-
-
|
|
125
|
-
-
|
|
126
|
+
|
|
127
|
+
- `IntegrationBase` - Base class for all integrations
|
|
128
|
+
- `Integration` - Domain aggregate using Proxy pattern
|
|
129
|
+
- Use cases: `CreateIntegration`, `UpdateIntegration`, `DeleteIntegration`
|
|
126
130
|
|
|
127
131
|
**Usage:**
|
|
132
|
+
|
|
128
133
|
```javascript
|
|
129
134
|
const { IntegrationBase } = require('@friggframework/core');
|
|
130
135
|
|
|
@@ -134,8 +139,8 @@ class SlackHubSpotSync extends IntegrationBase {
|
|
|
134
139
|
version: '2.1.0',
|
|
135
140
|
modules: {
|
|
136
141
|
slack: 'slack',
|
|
137
|
-
hubspot: 'hubspot'
|
|
138
|
-
}
|
|
142
|
+
hubspot: 'hubspot',
|
|
143
|
+
},
|
|
139
144
|
};
|
|
140
145
|
|
|
141
146
|
async onCreate({ integrationId }) {
|
|
@@ -152,30 +157,32 @@ class SlackHubSpotSync extends IntegrationBase {
|
|
|
152
157
|
MongoDB integration with Mongoose ODM.
|
|
153
158
|
|
|
154
159
|
**Key Components:**
|
|
155
|
-
|
|
156
|
-
-
|
|
157
|
-
-
|
|
160
|
+
|
|
161
|
+
- Connection management
|
|
162
|
+
- Pre-built models (User, Integration, Credential, etc.)
|
|
163
|
+
- Schema definitions
|
|
158
164
|
|
|
159
165
|
**Usage:**
|
|
166
|
+
|
|
160
167
|
```javascript
|
|
161
|
-
const {
|
|
162
|
-
connectToDatabase,
|
|
163
|
-
IntegrationModel,
|
|
164
|
-
UserModel
|
|
168
|
+
const {
|
|
169
|
+
connectToDatabase,
|
|
170
|
+
IntegrationModel,
|
|
171
|
+
UserModel,
|
|
165
172
|
} = require('@friggframework/core');
|
|
166
173
|
|
|
167
174
|
await connectToDatabase();
|
|
168
175
|
|
|
169
176
|
// Query integrations
|
|
170
|
-
const userIntegrations = await IntegrationModel.find({
|
|
177
|
+
const userIntegrations = await IntegrationModel.find({
|
|
171
178
|
userId: 'user-123',
|
|
172
|
-
status: 'ENABLED'
|
|
179
|
+
status: 'ENABLED',
|
|
173
180
|
});
|
|
174
181
|
|
|
175
182
|
// Create user
|
|
176
183
|
const user = new UserModel({
|
|
177
184
|
email: 'user@example.com',
|
|
178
|
-
name: 'John Doe'
|
|
185
|
+
name: 'John Doe',
|
|
179
186
|
});
|
|
180
187
|
await user.save();
|
|
181
188
|
```
|
|
@@ -185,6 +192,7 @@ await user.save();
|
|
|
185
192
|
AES-256-GCM encryption for sensitive data.
|
|
186
193
|
|
|
187
194
|
**Usage:**
|
|
195
|
+
|
|
188
196
|
```javascript
|
|
189
197
|
const { Encrypt, Cryptor } = require('@friggframework/core');
|
|
190
198
|
|
|
@@ -194,10 +202,12 @@ const decrypted = Encrypt.decrypt(encrypted);
|
|
|
194
202
|
|
|
195
203
|
// Advanced encryption with custom key
|
|
196
204
|
const cryptor = new Cryptor(process.env.CUSTOM_KEY);
|
|
197
|
-
const secureData = cryptor.encrypt(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
205
|
+
const secureData = cryptor.encrypt(
|
|
206
|
+
JSON.stringify({
|
|
207
|
+
accessToken: 'oauth-token',
|
|
208
|
+
refreshToken: 'refresh-token',
|
|
209
|
+
})
|
|
210
|
+
);
|
|
201
211
|
```
|
|
202
212
|
|
|
203
213
|
### 5. Error Handling (`/errors`)
|
|
@@ -205,11 +215,12 @@ const secureData = cryptor.encrypt(JSON.stringify({
|
|
|
205
215
|
Standardized error types with proper HTTP status codes.
|
|
206
216
|
|
|
207
217
|
**Usage:**
|
|
218
|
+
|
|
208
219
|
```javascript
|
|
209
|
-
const {
|
|
210
|
-
BaseError,
|
|
211
|
-
RequiredPropertyError,
|
|
212
|
-
FetchError
|
|
220
|
+
const {
|
|
221
|
+
BaseError,
|
|
222
|
+
RequiredPropertyError,
|
|
223
|
+
FetchError,
|
|
213
224
|
} = require('@friggframework/core');
|
|
214
225
|
|
|
215
226
|
// Custom business logic error
|
|
@@ -218,13 +229,13 @@ throw new RequiredPropertyError('userId is required');
|
|
|
218
229
|
// API communication error
|
|
219
230
|
throw new FetchError('Failed to fetch data from external API', {
|
|
220
231
|
statusCode: 404,
|
|
221
|
-
response: errorResponse
|
|
232
|
+
response: errorResponse,
|
|
222
233
|
});
|
|
223
234
|
|
|
224
235
|
// Base error with custom properties
|
|
225
236
|
throw new BaseError('Integration failed', {
|
|
226
237
|
integrationId: 'int-123',
|
|
227
|
-
errorCode: 'SYNC_FAILED'
|
|
238
|
+
errorCode: 'SYNC_FAILED',
|
|
228
239
|
});
|
|
229
240
|
```
|
|
230
241
|
|
|
@@ -233,6 +244,7 @@ throw new BaseError('Integration failed', {
|
|
|
233
244
|
Structured logging with debug capabilities.
|
|
234
245
|
|
|
235
246
|
**Usage:**
|
|
247
|
+
|
|
236
248
|
```javascript
|
|
237
249
|
const { debug, initDebugLog, flushDebugLog } = require('@friggframework/core');
|
|
238
250
|
|
|
@@ -240,9 +252,9 @@ const { debug, initDebugLog, flushDebugLog } = require('@friggframework/core');
|
|
|
240
252
|
initDebugLog('integration:slack');
|
|
241
253
|
|
|
242
254
|
// Log debug information
|
|
243
|
-
debug('Processing webhook payload', {
|
|
255
|
+
debug('Processing webhook payload', {
|
|
244
256
|
eventType: 'contact.created',
|
|
245
|
-
payload: webhookData
|
|
257
|
+
payload: webhookData,
|
|
246
258
|
});
|
|
247
259
|
|
|
248
260
|
// Flush logs (useful in serverless environments)
|
|
@@ -254,27 +266,31 @@ await flushDebugLog();
|
|
|
254
266
|
Comprehensive user authentication and authorization system supporting both individual and organizational users.
|
|
255
267
|
|
|
256
268
|
**Key Classes:**
|
|
257
|
-
|
|
258
|
-
-
|
|
259
|
-
-
|
|
269
|
+
|
|
270
|
+
- `User` - Domain aggregate for user entities
|
|
271
|
+
- `UserRepository` - Data access for user operations
|
|
272
|
+
- Use cases: `LoginUser`, `CreateIndividualUser`, `CreateOrganizationUser`, `GetUserFromBearerToken`
|
|
260
273
|
|
|
261
274
|
**User Types:**
|
|
262
|
-
|
|
263
|
-
-
|
|
264
|
-
-
|
|
275
|
+
|
|
276
|
+
- **Individual Users**: Personal accounts with email/username authentication
|
|
277
|
+
- **Organization Users**: Business accounts with organization-level access
|
|
278
|
+
- **Hybrid Mode**: Support for both user types simultaneously
|
|
265
279
|
|
|
266
280
|
**Authentication Methods:**
|
|
267
|
-
|
|
268
|
-
-
|
|
269
|
-
-
|
|
281
|
+
|
|
282
|
+
- **Password-based**: Traditional username/password authentication
|
|
283
|
+
- **Token-based**: Bearer token authentication with session management
|
|
284
|
+
- **App-based**: External app user ID authentication (passwordless)
|
|
270
285
|
|
|
271
286
|
**Usage:**
|
|
287
|
+
|
|
272
288
|
```javascript
|
|
273
|
-
const {
|
|
274
|
-
LoginUser,
|
|
275
|
-
CreateIndividualUser,
|
|
289
|
+
const {
|
|
290
|
+
LoginUser,
|
|
291
|
+
CreateIndividualUser,
|
|
276
292
|
GetUserFromBearerToken,
|
|
277
|
-
UserRepository
|
|
293
|
+
UserRepository,
|
|
278
294
|
} = require('@friggframework/core');
|
|
279
295
|
|
|
280
296
|
// Configure user behavior in app definition
|
|
@@ -282,7 +298,7 @@ const userConfig = {
|
|
|
282
298
|
usePassword: true,
|
|
283
299
|
primary: 'individual', // or 'organization'
|
|
284
300
|
individualUserRequired: true,
|
|
285
|
-
organizationUserRequired: false
|
|
301
|
+
organizationUserRequired: false,
|
|
286
302
|
};
|
|
287
303
|
|
|
288
304
|
const userRepository = new UserRepository({ userConfig });
|
|
@@ -293,18 +309,21 @@ const user = await createUser.execute({
|
|
|
293
309
|
email: 'user@example.com',
|
|
294
310
|
username: 'john_doe',
|
|
295
311
|
password: 'secure_password',
|
|
296
|
-
appUserId: 'external_user_123' // Optional external reference
|
|
312
|
+
appUserId: 'external_user_123', // Optional external reference
|
|
297
313
|
});
|
|
298
314
|
|
|
299
315
|
// Login user
|
|
300
316
|
const loginUser = new LoginUser({ userRepository, userConfig });
|
|
301
317
|
const authenticatedUser = await loginUser.execute({
|
|
302
318
|
username: 'john_doe',
|
|
303
|
-
password: 'secure_password'
|
|
319
|
+
password: 'secure_password',
|
|
304
320
|
});
|
|
305
321
|
|
|
306
322
|
// Token-based authentication
|
|
307
|
-
const getUserFromToken = new GetUserFromBearerToken({
|
|
323
|
+
const getUserFromToken = new GetUserFromBearerToken({
|
|
324
|
+
userRepository,
|
|
325
|
+
userConfig,
|
|
326
|
+
});
|
|
308
327
|
const user = await getUserFromToken.execute('Bearer eyJhbGciOiJIUzI1NiIs...');
|
|
309
328
|
|
|
310
329
|
// Access user properties
|
|
@@ -319,12 +338,13 @@ console.log('Organization user:', user.getOrganizationUser());
|
|
|
319
338
|
AWS Lambda-specific utilities and helpers.
|
|
320
339
|
|
|
321
340
|
**Usage:**
|
|
341
|
+
|
|
322
342
|
```javascript
|
|
323
343
|
const { TimeoutCatcher } = require('@friggframework/core');
|
|
324
344
|
|
|
325
345
|
exports.handler = async (event, context) => {
|
|
326
346
|
const timeoutCatcher = new TimeoutCatcher(context);
|
|
327
|
-
|
|
347
|
+
|
|
328
348
|
try {
|
|
329
349
|
// Long-running integration process
|
|
330
350
|
const result = await processIntegrationSync(event);
|
|
@@ -353,11 +373,11 @@ User behavior is configured in the app definition, allowing you to customize aut
|
|
|
353
373
|
const appDefinition = {
|
|
354
374
|
integrations: [HubSpotIntegration],
|
|
355
375
|
user: {
|
|
356
|
-
usePassword: true,
|
|
357
|
-
primary: 'individual',
|
|
358
|
-
organizationUserRequired: true,
|
|
359
|
-
individualUserRequired: true,
|
|
360
|
-
}
|
|
376
|
+
usePassword: true, // Enable password authentication
|
|
377
|
+
primary: 'individual', // Primary user type: 'individual' or 'organization'
|
|
378
|
+
organizationUserRequired: true, // Require organization user
|
|
379
|
+
individualUserRequired: true, // Require individual user
|
|
380
|
+
},
|
|
361
381
|
};
|
|
362
382
|
```
|
|
363
383
|
|
|
@@ -372,20 +392,20 @@ const { User } = require('@friggframework/core');
|
|
|
372
392
|
const user = new User(individualUser, organizationUser, usePassword, primary);
|
|
373
393
|
|
|
374
394
|
// Access methods
|
|
375
|
-
user.getId()
|
|
376
|
-
user.getPrimaryUser()
|
|
377
|
-
user.getIndividualUser()
|
|
378
|
-
user.getOrganizationUser()
|
|
395
|
+
user.getId(); // Get primary user ID
|
|
396
|
+
user.getPrimaryUser(); // Get primary user based on config
|
|
397
|
+
user.getIndividualUser(); // Get individual user
|
|
398
|
+
user.getOrganizationUser(); // Get organization user
|
|
379
399
|
|
|
380
400
|
// Validation methods
|
|
381
|
-
user.isPasswordRequired()
|
|
382
|
-
user.isPasswordValid(password) // Validate password
|
|
383
|
-
user.isIndividualUserRequired() // Check individual user requirement
|
|
384
|
-
user.isOrganizationUserRequired() // Check organization user requirement
|
|
401
|
+
user.isPasswordRequired(); // Check if password is required
|
|
402
|
+
user.isPasswordValid(password); // Validate password
|
|
403
|
+
user.isIndividualUserRequired(); // Check individual user requirement
|
|
404
|
+
user.isOrganizationUserRequired(); // Check organization user requirement
|
|
385
405
|
|
|
386
406
|
// Configuration methods
|
|
387
|
-
user.setIndividualUser(individualUser)
|
|
388
|
-
user.setOrganizationUser(organizationUser)
|
|
407
|
+
user.setIndividualUser(individualUser);
|
|
408
|
+
user.setOrganizationUser(organizationUser);
|
|
389
409
|
```
|
|
390
410
|
|
|
391
411
|
### Database Models
|
|
@@ -421,11 +441,11 @@ The user system uses MongoDB with Mongoose for data persistence:
|
|
|
421
441
|
|
|
422
442
|
### Security Features
|
|
423
443
|
|
|
424
|
-
-
|
|
425
|
-
-
|
|
426
|
-
-
|
|
427
|
-
-
|
|
428
|
-
-
|
|
444
|
+
- **Password Hashing**: Uses bcrypt with configurable salt rounds
|
|
445
|
+
- **Token Management**: Secure session tokens with expiration
|
|
446
|
+
- **Unique Constraints**: Enforced username and email uniqueness
|
|
447
|
+
- **External References**: Support for external app user/org IDs
|
|
448
|
+
- **Flexible Authentication**: Multiple authentication methods
|
|
429
449
|
|
|
430
450
|
## Hexagonal Architecture
|
|
431
451
|
|
|
@@ -446,7 +466,9 @@ class UpdateIntegrationStatus {
|
|
|
446
466
|
}
|
|
447
467
|
|
|
448
468
|
// Domain operation
|
|
449
|
-
const integration = await this.integrationRepository.findById(
|
|
469
|
+
const integration = await this.integrationRepository.findById(
|
|
470
|
+
integrationId
|
|
471
|
+
);
|
|
450
472
|
if (!integration) {
|
|
451
473
|
throw new Error('Integration not found');
|
|
452
474
|
}
|
|
@@ -454,7 +476,7 @@ class UpdateIntegrationStatus {
|
|
|
454
476
|
// Update and persist
|
|
455
477
|
integration.status = newStatus;
|
|
456
478
|
integration.updatedAt = new Date();
|
|
457
|
-
|
|
479
|
+
|
|
458
480
|
return await this.integrationRepository.save(integration);
|
|
459
481
|
}
|
|
460
482
|
}
|
|
@@ -484,7 +506,7 @@ class IntegrationRepository {
|
|
|
484
506
|
userId,
|
|
485
507
|
config,
|
|
486
508
|
status: 'NEW',
|
|
487
|
-
createdAt: new Date()
|
|
509
|
+
createdAt: new Date(),
|
|
488
510
|
});
|
|
489
511
|
return await integration.save();
|
|
490
512
|
}
|
|
@@ -500,7 +522,7 @@ const Integration = new Proxy(class {}, {
|
|
|
500
522
|
construct(target, args) {
|
|
501
523
|
const [params] = args;
|
|
502
524
|
const instance = new params.integrationClass(params);
|
|
503
|
-
|
|
525
|
+
|
|
504
526
|
// Attach domain properties
|
|
505
527
|
Object.assign(instance, {
|
|
506
528
|
id: params.id,
|
|
@@ -508,11 +530,11 @@ const Integration = new Proxy(class {}, {
|
|
|
508
530
|
entities: params.entities,
|
|
509
531
|
config: params.config,
|
|
510
532
|
status: params.status,
|
|
511
|
-
modules: params.modules
|
|
533
|
+
modules: params.modules,
|
|
512
534
|
});
|
|
513
535
|
|
|
514
536
|
return instance;
|
|
515
|
-
}
|
|
537
|
+
},
|
|
516
538
|
});
|
|
517
539
|
```
|
|
518
540
|
|
|
@@ -565,7 +587,7 @@ class HubSpotIntegration extends IntegrationBase {
|
|
|
565
587
|
|
|
566
588
|
constructor() {
|
|
567
589
|
super();
|
|
568
|
-
|
|
590
|
+
|
|
569
591
|
// Define event handlers for various integration actions
|
|
570
592
|
this.events = {
|
|
571
593
|
// Webhook handler with real-time WebSocket broadcasting
|
|
@@ -574,7 +596,8 @@ class HubSpotIntegration extends IntegrationBase {
|
|
|
574
596
|
console.log('Received HubSpot webhook:', data);
|
|
575
597
|
|
|
576
598
|
// Broadcast to all connected WebSocket clients
|
|
577
|
-
const activeConnections =
|
|
599
|
+
const activeConnections =
|
|
600
|
+
await WebsocketConnection.getActiveConnections();
|
|
578
601
|
const message = JSON.stringify({
|
|
579
602
|
type: 'HUBSPOT_WEBHOOK',
|
|
580
603
|
data,
|
|
@@ -585,16 +608,17 @@ class HubSpotIntegration extends IntegrationBase {
|
|
|
585
608
|
});
|
|
586
609
|
},
|
|
587
610
|
},
|
|
588
|
-
|
|
611
|
+
|
|
589
612
|
// User action: Get sample data with formatted table output
|
|
590
613
|
[FriggConstants.defaultEvents.GET_SAMPLE_DATA]: {
|
|
591
614
|
type: FriggConstants.eventTypes.USER_ACTION,
|
|
592
615
|
handler: this.getSampleData,
|
|
593
616
|
title: 'Get Sample Data',
|
|
594
|
-
description:
|
|
617
|
+
description:
|
|
618
|
+
'Get sample data from HubSpot and display in a formatted table',
|
|
595
619
|
userActionType: 'QUICK_ACTION',
|
|
596
620
|
},
|
|
597
|
-
|
|
621
|
+
|
|
598
622
|
// User action: List available objects
|
|
599
623
|
GET_OBJECT_LIST: {
|
|
600
624
|
type: FriggConstants.eventTypes.USER_ACTION,
|
|
@@ -603,7 +627,7 @@ class HubSpotIntegration extends IntegrationBase {
|
|
|
603
627
|
description: 'Get list of available HubSpot objects',
|
|
604
628
|
userActionType: 'DATA',
|
|
605
629
|
},
|
|
606
|
-
|
|
630
|
+
|
|
607
631
|
// User action: Create records with dynamic forms
|
|
608
632
|
CREATE_RECORD: {
|
|
609
633
|
type: FriggConstants.eventTypes.USER_ACTION,
|
|
@@ -613,7 +637,7 @@ class HubSpotIntegration extends IntegrationBase {
|
|
|
613
637
|
userActionType: 'DATA',
|
|
614
638
|
},
|
|
615
639
|
};
|
|
616
|
-
|
|
640
|
+
|
|
617
641
|
// Extension system for modular functionality
|
|
618
642
|
this.extensions = {
|
|
619
643
|
hubspotWebhooks: {
|
|
@@ -687,7 +711,7 @@ class HubSpotIntegration extends IntegrationBase {
|
|
|
687
711
|
let res;
|
|
688
712
|
const objectType = args.objectType;
|
|
689
713
|
delete args.objectType;
|
|
690
|
-
|
|
714
|
+
|
|
691
715
|
switch (objectType.toLowerCase()) {
|
|
692
716
|
case 'deal':
|
|
693
717
|
res = await this.hubspot.api.createDeal({ ...args });
|
|
@@ -718,7 +742,7 @@ class HubSpotIntegration extends IntegrationBase {
|
|
|
718
742
|
},
|
|
719
743
|
required: [],
|
|
720
744
|
};
|
|
721
|
-
|
|
745
|
+
|
|
722
746
|
let uiSchema = {
|
|
723
747
|
type: 'HorizontalLayout',
|
|
724
748
|
elements: [
|
|
@@ -744,7 +768,7 @@ class HubSpotIntegration extends IntegrationBase {
|
|
|
744
768
|
{ type: 'Control', scope: '#/properties/amount' }
|
|
745
769
|
);
|
|
746
770
|
break;
|
|
747
|
-
|
|
771
|
+
|
|
748
772
|
case 'company':
|
|
749
773
|
jsonSchema.properties = {
|
|
750
774
|
...jsonSchema.properties,
|
|
@@ -757,7 +781,7 @@ class HubSpotIntegration extends IntegrationBase {
|
|
|
757
781
|
{ type: 'Control', scope: '#/properties/website' }
|
|
758
782
|
);
|
|
759
783
|
break;
|
|
760
|
-
|
|
784
|
+
|
|
761
785
|
case 'contact':
|
|
762
786
|
jsonSchema.properties = {
|
|
763
787
|
...jsonSchema.properties,
|
|
@@ -765,16 +789,25 @@ class HubSpotIntegration extends IntegrationBase {
|
|
|
765
789
|
lastname: { type: 'string', title: 'Last Name' },
|
|
766
790
|
email: { type: 'string', title: 'Email Address' },
|
|
767
791
|
};
|
|
768
|
-
jsonSchema.required = [
|
|
792
|
+
jsonSchema.required = [
|
|
793
|
+
'firstname',
|
|
794
|
+
'lastname',
|
|
795
|
+
'email',
|
|
796
|
+
];
|
|
769
797
|
uiSchema.elements.push(
|
|
770
|
-
{
|
|
798
|
+
{
|
|
799
|
+
type: 'Control',
|
|
800
|
+
scope: '#/properties/firstname',
|
|
801
|
+
},
|
|
771
802
|
{ type: 'Control', scope: '#/properties/lastname' },
|
|
772
803
|
{ type: 'Control', scope: '#/properties/email' }
|
|
773
804
|
);
|
|
774
805
|
break;
|
|
775
|
-
|
|
806
|
+
|
|
776
807
|
default:
|
|
777
|
-
throw new Error(
|
|
808
|
+
throw new Error(
|
|
809
|
+
`Unsupported object type: ${data.name}`
|
|
810
|
+
);
|
|
778
811
|
}
|
|
779
812
|
|
|
780
813
|
return {
|
|
@@ -796,28 +829,25 @@ module.exports = HubSpotIntegration;
|
|
|
796
829
|
```
|
|
797
830
|
|
|
798
831
|
index.js
|
|
832
|
+
|
|
799
833
|
```js
|
|
800
834
|
const HubSpotIntegration = require('./src/integrations/HubSpotIntegration');
|
|
801
835
|
|
|
802
836
|
const appDefinition = {
|
|
803
|
-
integrations: [
|
|
804
|
-
HubSpotIntegration,
|
|
805
|
-
],
|
|
837
|
+
integrations: [HubSpotIntegration],
|
|
806
838
|
user: {
|
|
807
839
|
usePassword: true,
|
|
808
840
|
primary: 'individual',
|
|
809
841
|
organizationUserRequired: true,
|
|
810
842
|
individualUserRequired: true,
|
|
811
|
-
}
|
|
812
|
-
}
|
|
843
|
+
},
|
|
844
|
+
};
|
|
813
845
|
|
|
814
846
|
module.exports = {
|
|
815
847
|
Definition: appDefinition,
|
|
816
|
-
}
|
|
817
|
-
|
|
848
|
+
};
|
|
818
849
|
```
|
|
819
850
|
|
|
820
|
-
|
|
821
851
|
### Key Features Demonstrated
|
|
822
852
|
|
|
823
853
|
This real-world example showcases:
|
|
@@ -830,7 +860,6 @@ This real-world example showcases:
|
|
|
830
860
|
**🔗 Deep Linking**: Direct links to HubSpot records in formatted data
|
|
831
861
|
**⚡ Real-time Updates**: WebSocket connections for live data streaming
|
|
832
862
|
|
|
833
|
-
|
|
834
863
|
## Testing
|
|
835
864
|
|
|
836
865
|
### Running Tests
|
|
@@ -860,13 +889,15 @@ describe('CreateIntegration Use-Case', () => {
|
|
|
860
889
|
useCase = new CreateIntegration({
|
|
861
890
|
integrationRepository,
|
|
862
891
|
integrationClasses: [TestIntegration],
|
|
863
|
-
moduleFactory
|
|
892
|
+
moduleFactory,
|
|
864
893
|
});
|
|
865
894
|
});
|
|
866
895
|
|
|
867
896
|
describe('happy path', () => {
|
|
868
897
|
it('creates an integration and returns DTO', async () => {
|
|
869
|
-
const result = await useCase.execute(['entity-1'], 'user-1', {
|
|
898
|
+
const result = await useCase.execute(['entity-1'], 'user-1', {
|
|
899
|
+
type: 'test',
|
|
900
|
+
});
|
|
870
901
|
expect(result.id).toBeDefined();
|
|
871
902
|
expect(result.status).toBe('NEW');
|
|
872
903
|
});
|
|
@@ -874,8 +905,9 @@ describe('CreateIntegration Use-Case', () => {
|
|
|
874
905
|
|
|
875
906
|
describe('error cases', () => {
|
|
876
907
|
it('throws error for unknown integration type', async () => {
|
|
877
|
-
await expect(
|
|
878
|
-
.
|
|
908
|
+
await expect(
|
|
909
|
+
useCase.execute(['entity-1'], 'user-1', { type: 'unknown' })
|
|
910
|
+
).rejects.toThrow('No integration class found for type: unknown');
|
|
879
911
|
});
|
|
880
912
|
});
|
|
881
913
|
});
|
|
@@ -886,7 +918,10 @@ describe('CreateIntegration Use-Case', () => {
|
|
|
886
918
|
The framework provides test doubles for external dependencies:
|
|
887
919
|
|
|
888
920
|
```javascript
|
|
889
|
-
const {
|
|
921
|
+
const {
|
|
922
|
+
TestIntegrationRepository,
|
|
923
|
+
TestModuleFactory,
|
|
924
|
+
} = require('@friggframework/core/test');
|
|
890
925
|
|
|
891
926
|
// Mock repository for testing
|
|
892
927
|
const testRepo = new TestIntegrationRepository();
|
|
@@ -946,7 +981,7 @@ const {
|
|
|
946
981
|
CreateIntegration,
|
|
947
982
|
UpdateIntegration,
|
|
948
983
|
DeleteIntegration,
|
|
949
|
-
|
|
984
|
+
|
|
950
985
|
// Modules
|
|
951
986
|
OAuth2Requester,
|
|
952
987
|
ApiKeyRequester,
|
|
@@ -956,25 +991,25 @@ const {
|
|
|
956
991
|
connectToDatabase,
|
|
957
992
|
mongoose,
|
|
958
993
|
UserModel,
|
|
959
|
-
|
|
994
|
+
|
|
960
995
|
// Utilities
|
|
961
996
|
Encrypt,
|
|
962
997
|
Cryptor,
|
|
963
998
|
BaseError,
|
|
964
999
|
debug,
|
|
965
|
-
TimeoutCatcher
|
|
1000
|
+
TimeoutCatcher,
|
|
966
1001
|
} = require('@friggframework/core');
|
|
967
1002
|
```
|
|
968
1003
|
|
|
969
1004
|
### Environment Configuration
|
|
970
1005
|
|
|
971
|
-
| Variable
|
|
972
|
-
|
|
973
|
-
| `MONGO_URI`
|
|
974
|
-
| `FRIGG_ENCRYPTION_KEY` | Yes
|
|
975
|
-
| `AWS_REGION`
|
|
976
|
-
| `DEBUG`
|
|
977
|
-
| `LOG_LEVEL`
|
|
1006
|
+
| Variable | Required | Description |
|
|
1007
|
+
| ---------------------- | -------- | ---------------------------------------- |
|
|
1008
|
+
| `MONGO_URI` | Yes | MongoDB connection string |
|
|
1009
|
+
| `FRIGG_ENCRYPTION_KEY` | Yes | 256-bit encryption key |
|
|
1010
|
+
| `AWS_REGION` | No | AWS region for services |
|
|
1011
|
+
| `DEBUG` | No | Debug logging pattern |
|
|
1012
|
+
| `LOG_LEVEL` | No | Logging level (debug, info, warn, error) |
|
|
978
1013
|
|
|
979
1014
|
## License
|
|
980
1015
|
|
|
@@ -984,9 +1019,9 @@ This project is licensed under the MIT License - see the [LICENSE.md](../../LICE
|
|
|
984
1019
|
|
|
985
1020
|
## Support
|
|
986
1021
|
|
|
987
|
-
-
|
|
988
|
-
-
|
|
989
|
-
-
|
|
990
|
-
-
|
|
1022
|
+
- 📖 [Documentation](https://docs.friggframework.org)
|
|
1023
|
+
- 💬 [Community Slack](https://friggframework.slack.com)
|
|
1024
|
+
- 🐛 [Issue Tracker](https://github.com/friggframework/frigg/issues)
|
|
1025
|
+
- 📧 [Email Support](mailto:support@friggframework.org)
|
|
991
1026
|
|
|
992
1027
|
Built with ❤️ by the Frigg Framework team.
|