@friggframework/core 2.0.0--canary.548.c8ae0ca.0 → 2.0.0--canary.545.c40eca4.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 +474 -0
- package/handlers/routers/auth.js +51 -9
- 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 +84 -283
- package/handlers/routers/integration-defined-routers.js +44 -33
- package/handlers/routers/integration-webhook-routers.js +73 -52
- 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/user.js +30 -14
- package/handlers/routers/websocket.js +18 -4
- package/handlers/use-cases/check-integrations-health-use-case.js +13 -6
- package/handlers/workers/db-migration.js +74 -35
- package/handlers/workers/integration-defined-workers.js +31 -20
- 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
|
@@ -2,12 +2,12 @@ const Boom = require('@hapi/boom');
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Use case for authenticating a user using multiple authentication strategies.
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
6
|
* Supports three authentication modes in priority order:
|
|
7
7
|
* 1. Shared Secret (backend-to-backend with x-frigg-api-key + x-frigg headers)
|
|
8
8
|
* 2. Adopter JWT (custom JWT authentication)
|
|
9
9
|
* 3. Frigg Native Token (bearer token from /user/login)
|
|
10
|
-
*
|
|
10
|
+
*
|
|
11
11
|
* x-frigg-appUserId and x-frigg-appOrgId headers are automatically supported
|
|
12
12
|
* for user identification with any auth mode. When present with JWT or Frigg
|
|
13
13
|
* tokens, they are validated to match the authenticated user.
|
|
@@ -123,5 +123,3 @@ class AuthenticateUser {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
module.exports = { AuthenticateUser };
|
|
126
|
-
|
|
127
|
-
|
|
@@ -33,7 +33,7 @@ class AuthenticateWithSharedSecret {
|
|
|
33
33
|
if (!expectedSecret) {
|
|
34
34
|
throw Boom.badImplementation(
|
|
35
35
|
'FRIGG_API_KEY environment variable is not configured. ' +
|
|
36
|
-
|
|
36
|
+
'Set FRIGG_API_KEY to enable shared secret authentication.'
|
|
37
37
|
);
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -39,13 +39,14 @@ class CreateIndividualUser {
|
|
|
39
39
|
const appUserId = get(params, 'appUserId', null);
|
|
40
40
|
const organizationUserId = get(params, 'organizationUserId', null);
|
|
41
41
|
|
|
42
|
-
const individualUserData =
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
const individualUserData =
|
|
43
|
+
await this.userRepository.createIndividualUser({
|
|
44
|
+
email,
|
|
45
|
+
username,
|
|
46
|
+
hashword,
|
|
47
|
+
appUserId,
|
|
48
|
+
organizationUser: organizationUserId,
|
|
49
|
+
});
|
|
49
50
|
|
|
50
51
|
return new User(
|
|
51
52
|
individualUserData,
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const { get } = require('../../assertions');
|
|
2
|
+
const Boom = require('@hapi/boom');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Use case for deleting a user.
|
|
6
|
+
* @class DeleteUser
|
|
7
|
+
*/
|
|
8
|
+
class DeleteUser {
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new DeleteUser instance.
|
|
11
|
+
* @param {Object} params - Configuration parameters.
|
|
12
|
+
* @param {import('../repositories/user-repository-interface').UserRepositoryInterface} params.userRepository - Repository for user data operations.
|
|
13
|
+
*/
|
|
14
|
+
constructor({ userRepository }) {
|
|
15
|
+
this.userRepository = userRepository;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Executes the use case.
|
|
20
|
+
* @async
|
|
21
|
+
* @param {string} userId - The ID of the user to delete.
|
|
22
|
+
* @returns {Promise<boolean>} True if user was deleted successfully.
|
|
23
|
+
* @throws {Boom} If userId is not provided or user not found.
|
|
24
|
+
*/
|
|
25
|
+
async execute(userId) {
|
|
26
|
+
if (!userId) {
|
|
27
|
+
throw Boom.badRequest('userId is required');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// First check if user exists
|
|
31
|
+
const user = await this.userRepository.findUserById(userId);
|
|
32
|
+
if (!user) {
|
|
33
|
+
throw Boom.notFound(`User with id ${userId} not found`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Delete the user
|
|
37
|
+
const deleted = await this.userRepository.deleteUser(userId);
|
|
38
|
+
|
|
39
|
+
if (!deleted) {
|
|
40
|
+
throw Boom.internal(`Failed to delete user with id ${userId}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = { DeleteUser };
|
|
@@ -41,7 +41,10 @@ class GetUserFromBearerToken {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
if (this.userConfig.primary === 'organization') {
|
|
44
|
-
const organizationUserData =
|
|
44
|
+
const organizationUserData =
|
|
45
|
+
await this.userRepository.findOrganizationUserById(
|
|
46
|
+
sessionToken.user
|
|
47
|
+
);
|
|
45
48
|
|
|
46
49
|
if (!organizationUserData) {
|
|
47
50
|
throw Boom.unauthorized('Organization User Not Found');
|
|
@@ -57,7 +60,8 @@ class GetUserFromBearerToken {
|
|
|
57
60
|
);
|
|
58
61
|
}
|
|
59
62
|
|
|
60
|
-
const individualUserData =
|
|
63
|
+
const individualUserData =
|
|
64
|
+
await this.userRepository.findIndividualUserById(sessionToken.user);
|
|
61
65
|
|
|
62
66
|
if (!individualUserData) {
|
|
63
67
|
throw Boom.unauthorized('Individual User Not Found');
|
|
@@ -74,4 +78,4 @@ class GetUserFromBearerToken {
|
|
|
74
78
|
}
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
module.exports = { GetUserFromBearerToken };
|
|
81
|
+
module.exports = { GetUserFromBearerToken };
|
|
@@ -76,10 +76,11 @@ class GetUserFromXFriggHeaders {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
// Auto-link the users
|
|
79
|
-
individualUserData =
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
individualUserData =
|
|
80
|
+
await this.userRepository.linkIndividualToOrganization(
|
|
81
|
+
individualUserData.id,
|
|
82
|
+
organizationUserData.id
|
|
83
|
+
);
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
|
|
@@ -89,12 +90,13 @@ class GetUserFromXFriggHeaders {
|
|
|
89
90
|
appUserId &&
|
|
90
91
|
this.userConfig.individualUserRequired !== false
|
|
91
92
|
) {
|
|
92
|
-
individualUserData =
|
|
93
|
-
|
|
93
|
+
individualUserData = await this.userRepository.createIndividualUser(
|
|
94
|
+
{
|
|
94
95
|
appUserId,
|
|
95
96
|
username: `app-user-${appUserId}`,
|
|
96
97
|
email: `${appUserId}@app.local`,
|
|
97
|
-
}
|
|
98
|
+
}
|
|
99
|
+
);
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
if (
|
|
@@ -109,10 +111,11 @@ class GetUserFromXFriggHeaders {
|
|
|
109
111
|
|
|
110
112
|
// Link individual user to newly created org user if individual exists
|
|
111
113
|
if (individualUserData && organizationUserData) {
|
|
112
|
-
individualUserData =
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
individualUserData =
|
|
115
|
+
await this.userRepository.linkIndividualToOrganization(
|
|
116
|
+
individualUserData.id,
|
|
117
|
+
organizationUserData.id
|
|
118
|
+
);
|
|
116
119
|
}
|
|
117
120
|
}
|
|
118
121
|
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
const Boom = require('@hapi/boom');
|
|
2
|
-
const {
|
|
3
|
-
RequiredPropertyError,
|
|
4
|
-
} = require('../../errors');
|
|
2
|
+
const { RequiredPropertyError } = require('../../errors');
|
|
5
3
|
const { User } = require('../user');
|
|
6
4
|
|
|
7
5
|
/**
|
|
@@ -93,11 +91,11 @@ class LoginUser {
|
|
|
93
91
|
}
|
|
94
92
|
}
|
|
95
93
|
|
|
96
|
-
|
|
97
94
|
if (this.userConfig.organizationUserRequired) {
|
|
98
|
-
|
|
99
95
|
const organizationUserData =
|
|
100
|
-
await this.userRepository.findOrganizationUserByAppOrgId(
|
|
96
|
+
await this.userRepository.findOrganizationUserByAppOrgId(
|
|
97
|
+
appOrgId
|
|
98
|
+
);
|
|
101
99
|
|
|
102
100
|
if (!organizationUserData) {
|
|
103
101
|
throw Boom.unauthorized(`org user ${appOrgId} not found`);
|
|
@@ -115,8 +113,10 @@ class LoginUser {
|
|
|
115
113
|
return organizationUser;
|
|
116
114
|
}
|
|
117
115
|
|
|
118
|
-
throw new Error(
|
|
116
|
+
throw new Error(
|
|
117
|
+
'User configuration must require either individualUserRequired or organizationUserRequired'
|
|
118
|
+
);
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
module.exports = { LoginUser };
|
|
122
|
+
module.exports = { LoginUser };
|
package/user/user.js
CHANGED
|
@@ -14,7 +14,14 @@ class User {
|
|
|
14
14
|
* @param {boolean} [individualUserRequired=true] - Whether the user is required to have an individual user.
|
|
15
15
|
* @param {boolean} [organizationUserRequired=false] - Whether the user is required to have an organization user.
|
|
16
16
|
*/
|
|
17
|
-
constructor(
|
|
17
|
+
constructor(
|
|
18
|
+
individualUser = null,
|
|
19
|
+
organizationUser = null,
|
|
20
|
+
usePassword = false,
|
|
21
|
+
primary = 'individual',
|
|
22
|
+
individualUserRequired = true,
|
|
23
|
+
organizationUserRequired = false
|
|
24
|
+
) {
|
|
18
25
|
this.individualUser = individualUser;
|
|
19
26
|
this.organizationUser = organizationUser;
|
|
20
27
|
this.usePassword = usePassword;
|
|
@@ -109,12 +116,19 @@ class User {
|
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
// When primary is 'organization', also check linked individual user
|
|
112
|
-
if (
|
|
119
|
+
if (
|
|
120
|
+
this.config.primary === 'organization' &&
|
|
121
|
+
userIdStr === individualId
|
|
122
|
+
) {
|
|
113
123
|
return true;
|
|
114
124
|
}
|
|
115
125
|
|
|
116
126
|
// When primary is 'individual', also check linked organization user if required
|
|
117
|
-
if (
|
|
127
|
+
if (
|
|
128
|
+
this.config.primary === 'individual' &&
|
|
129
|
+
this.config.organizationUserRequired &&
|
|
130
|
+
userIdStr === organizationId
|
|
131
|
+
) {
|
|
118
132
|
return true;
|
|
119
133
|
}
|
|
120
134
|
|
|
@@ -122,4 +136,4 @@ class User {
|
|
|
122
136
|
}
|
|
123
137
|
}
|
|
124
138
|
|
|
125
|
-
module.exports = { User };
|
|
139
|
+
module.exports = { User };
|
package/utils/backend-path.js
CHANGED
|
@@ -4,7 +4,7 @@ const PACKAGE_JSON = 'package.json';
|
|
|
4
4
|
|
|
5
5
|
function findNearestBackendPackageJson() {
|
|
6
6
|
let currentDir = process.cwd();
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
// First check if we're in production by looking for package.json in the current directory
|
|
9
9
|
const rootPackageJson = path.join(currentDir, PACKAGE_JSON);
|
|
10
10
|
if (fs.existsSync(rootPackageJson)) {
|
|
@@ -35,4 +35,4 @@ function validateBackendPath(backendPath) {
|
|
|
35
35
|
module.exports = {
|
|
36
36
|
findNearestBackendPackageJson,
|
|
37
37
|
validateBackendPath,
|
|
38
|
-
};
|
|
38
|
+
};
|
package/utils/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
const { prisma } = require('../../database/prisma');
|
|
2
2
|
const {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} = require('@aws-sdk/client-apigatewaymanagementapi');
|
|
3
|
+
StaleConnectionError,
|
|
4
|
+
} = require('../websocket-message-sender-interface');
|
|
6
5
|
const {
|
|
7
6
|
toObjectId,
|
|
8
7
|
fromObjectId,
|
|
@@ -16,10 +15,19 @@ const {
|
|
|
16
15
|
WebsocketConnectionRepositoryInterface,
|
|
17
16
|
} = require('./websocket-connection-repository-interface');
|
|
18
17
|
|
|
18
|
+
/**
|
|
19
|
+
* DocumentDB WebSocket Connection Repository Adapter
|
|
20
|
+
*
|
|
21
|
+
* BREAKING CHANGE (v3): A messageSender must be explicitly provided for
|
|
22
|
+
* WebSocket send functionality. For AWS API Gateway, pass
|
|
23
|
+
* `new ApiGatewayMessageSender()` from @friggframework/provider-aws.
|
|
24
|
+
* See docs/architecture-decisions/010-decouple-aws-from-core.md for migration guide.
|
|
25
|
+
*/
|
|
19
26
|
class WebsocketConnectionRepositoryDocumentDB extends WebsocketConnectionRepositoryInterface {
|
|
20
|
-
constructor() {
|
|
27
|
+
constructor(messageSender = null) {
|
|
21
28
|
super();
|
|
22
29
|
this.prisma = prisma;
|
|
30
|
+
this._messageSender = messageSender;
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
async createConnection(connectionId) {
|
|
@@ -29,13 +37,21 @@ class WebsocketConnectionRepositoryDocumentDB extends WebsocketConnectionReposit
|
|
|
29
37
|
createdAt: now,
|
|
30
38
|
updatedAt: now,
|
|
31
39
|
};
|
|
32
|
-
const insertedId = await insertOne(
|
|
33
|
-
|
|
40
|
+
const insertedId = await insertOne(
|
|
41
|
+
this.prisma,
|
|
42
|
+
'WebsocketConnection',
|
|
43
|
+
document
|
|
44
|
+
);
|
|
45
|
+
const created = await findOne(this.prisma, 'WebsocketConnection', {
|
|
46
|
+
_id: insertedId,
|
|
47
|
+
});
|
|
34
48
|
return this._mapConnection(created);
|
|
35
49
|
}
|
|
36
50
|
|
|
37
51
|
async deleteConnection(connectionId) {
|
|
38
|
-
const result = await deleteOne(this.prisma, 'WebsocketConnection', {
|
|
52
|
+
const result = await deleteOne(this.prisma, 'WebsocketConnection', {
|
|
53
|
+
connectionId,
|
|
54
|
+
});
|
|
39
55
|
const deleted = result?.n ?? 0;
|
|
40
56
|
return { acknowledged: true, deletedCount: deleted };
|
|
41
57
|
}
|
|
@@ -52,21 +68,28 @@ class WebsocketConnectionRepositoryDocumentDB extends WebsocketConnectionReposit
|
|
|
52
68
|
{ projection: { connectionId: 1 } }
|
|
53
69
|
);
|
|
54
70
|
|
|
71
|
+
if (!this._messageSender) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
'WebsocketConnectionRepositoryDocumentDB requires a messageSender for send functionality.\n' +
|
|
74
|
+
'Pass one via constructor, e.g.:\n' +
|
|
75
|
+
' const { ApiGatewayMessageSender } = require("@friggframework/provider-aws");\n' +
|
|
76
|
+
' new WebsocketConnectionRepositoryDocumentDB(new ApiGatewayMessageSender())\n' +
|
|
77
|
+
'See docs/architecture-decisions/010-decouple-aws-from-core.md for migration guide.'
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
const sender = this._messageSender;
|
|
81
|
+
|
|
55
82
|
return connections.map((conn) => ({
|
|
56
83
|
connectionId: conn.connectionId,
|
|
57
84
|
send: async (data) => {
|
|
58
|
-
const apigwManagementApi = new ApiGatewayManagementApiClient({
|
|
59
|
-
endpoint: process.env.WEBSOCKET_API_ENDPOINT,
|
|
60
|
-
});
|
|
61
|
-
|
|
62
85
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
86
|
+
await sender.send(
|
|
87
|
+
conn.connectionId,
|
|
88
|
+
data,
|
|
89
|
+
process.env.WEBSOCKET_API_ENDPOINT
|
|
90
|
+
);
|
|
68
91
|
} catch (error) {
|
|
69
|
-
if (error
|
|
92
|
+
if (error instanceof StaleConnectionError) {
|
|
70
93
|
console.log(`Stale connection ${conn.connectionId}`);
|
|
71
94
|
await deleteMany(this.prisma, 'WebsocketConnection', {
|
|
72
95
|
connectionId: conn.connectionId,
|
|
@@ -80,14 +103,18 @@ class WebsocketConnectionRepositoryDocumentDB extends WebsocketConnectionReposit
|
|
|
80
103
|
}
|
|
81
104
|
|
|
82
105
|
async findConnection(connectionId) {
|
|
83
|
-
const doc = await findOne(this.prisma, 'WebsocketConnection', {
|
|
106
|
+
const doc = await findOne(this.prisma, 'WebsocketConnection', {
|
|
107
|
+
connectionId,
|
|
108
|
+
});
|
|
84
109
|
return doc ? this._mapConnection(doc) : null;
|
|
85
110
|
}
|
|
86
111
|
|
|
87
112
|
async findConnectionById(id) {
|
|
88
113
|
const objectId = toObjectId(id);
|
|
89
114
|
if (!objectId) return null;
|
|
90
|
-
const doc = await findOne(this.prisma, 'WebsocketConnection', {
|
|
115
|
+
const doc = await findOne(this.prisma, 'WebsocketConnection', {
|
|
116
|
+
_id: objectId,
|
|
117
|
+
});
|
|
91
118
|
return doc ? this._mapConnection(doc) : null;
|
|
92
119
|
}
|
|
93
120
|
|
|
@@ -115,5 +142,3 @@ class WebsocketConnectionRepositoryDocumentDB extends WebsocketConnectionReposit
|
|
|
115
142
|
}
|
|
116
143
|
|
|
117
144
|
module.exports = { WebsocketConnectionRepositoryDocumentDB };
|
|
118
|
-
|
|
119
|
-
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
const {
|
|
2
|
-
WebsocketConnectionRepositoryMongo,
|
|
3
|
-
} = require('./websocket-connection-repository-mongo');
|
|
4
1
|
const {
|
|
5
2
|
WebsocketConnectionRepositoryPostgres,
|
|
6
3
|
} = require('./websocket-connection-repository-postgres');
|
|
7
|
-
const {
|
|
8
|
-
WebsocketConnectionRepositoryDocumentDB,
|
|
9
|
-
} = require('./websocket-connection-repository-documentdb');
|
|
10
4
|
const config = require('../../database/config');
|
|
11
5
|
|
|
12
6
|
/**
|
|
@@ -20,12 +14,14 @@ function createWebsocketConnectionRepository() {
|
|
|
20
14
|
|
|
21
15
|
switch (dbType) {
|
|
22
16
|
case 'mongodb':
|
|
17
|
+
const { WebsocketConnectionRepositoryMongo } = require('./websocket-connection-repository-mongo');
|
|
23
18
|
return new WebsocketConnectionRepositoryMongo();
|
|
24
19
|
|
|
25
20
|
case 'postgresql':
|
|
26
21
|
return new WebsocketConnectionRepositoryPostgres();
|
|
27
22
|
|
|
28
23
|
case 'documentdb':
|
|
24
|
+
const { WebsocketConnectionRepositoryDocumentDB } = require('./websocket-connection-repository-documentdb');
|
|
29
25
|
return new WebsocketConnectionRepositoryDocumentDB();
|
|
30
26
|
|
|
31
27
|
default:
|
|
@@ -38,7 +34,7 @@ function createWebsocketConnectionRepository() {
|
|
|
38
34
|
module.exports = {
|
|
39
35
|
createWebsocketConnectionRepository,
|
|
40
36
|
// Export adapters for direct testing
|
|
41
|
-
WebsocketConnectionRepositoryMongo,
|
|
37
|
+
get WebsocketConnectionRepositoryMongo() { return require('./websocket-connection-repository-mongo').WebsocketConnectionRepositoryMongo; },
|
|
42
38
|
WebsocketConnectionRepositoryPostgres,
|
|
43
|
-
WebsocketConnectionRepositoryDocumentDB,
|
|
39
|
+
get WebsocketConnectionRepositoryDocumentDB() { return require('./websocket-connection-repository-documentdb').WebsocketConnectionRepositoryDocumentDB; },
|
|
44
40
|
};
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
const { prisma } = require('../../database/prisma');
|
|
2
2
|
const {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} = require('@aws-sdk/client-apigatewaymanagementapi');
|
|
3
|
+
StaleConnectionError,
|
|
4
|
+
} = require('../websocket-message-sender-interface');
|
|
6
5
|
const {
|
|
7
6
|
WebsocketConnectionRepositoryInterface,
|
|
8
7
|
} = require('./websocket-connection-repository-interface');
|
|
@@ -14,12 +13,17 @@ const {
|
|
|
14
13
|
* MongoDB-specific characteristics:
|
|
15
14
|
* - Uses String IDs (ObjectId)
|
|
16
15
|
* - No ID conversion needed (IDs are already strings)
|
|
17
|
-
*
|
|
16
|
+
*
|
|
17
|
+
* BREAKING CHANGE (v3): A messageSender must be explicitly provided for
|
|
18
|
+
* WebSocket send functionality. For AWS API Gateway, pass
|
|
19
|
+
* `new ApiGatewayMessageSender()` from @friggframework/provider-aws.
|
|
20
|
+
* See docs/architecture-decisions/010-decouple-aws-from-core.md for migration guide.
|
|
18
21
|
*/
|
|
19
22
|
class WebsocketConnectionRepositoryMongo extends WebsocketConnectionRepositoryInterface {
|
|
20
|
-
constructor() {
|
|
23
|
+
constructor(messageSender = null) {
|
|
21
24
|
super();
|
|
22
25
|
this.prisma = prisma;
|
|
26
|
+
this._messageSender = messageSender;
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
/**
|
|
@@ -74,25 +78,31 @@ class WebsocketConnectionRepositoryMongo extends WebsocketConnectionRepositoryIn
|
|
|
74
78
|
select: { connectionId: true },
|
|
75
79
|
});
|
|
76
80
|
|
|
81
|
+
if (!this._messageSender) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
'WebsocketConnectionRepositoryMongo requires a messageSender for send functionality.\n' +
|
|
84
|
+
'Pass one via constructor, e.g.:\n' +
|
|
85
|
+
' const { ApiGatewayMessageSender } = require("@friggframework/provider-aws");\n' +
|
|
86
|
+
' new WebsocketConnectionRepositoryMongo(new ApiGatewayMessageSender())\n' +
|
|
87
|
+
'See docs/architecture-decisions/010-decouple-aws-from-core.md for migration guide.'
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const sender = this._messageSender;
|
|
91
|
+
|
|
77
92
|
return connections.map((conn) => ({
|
|
78
93
|
connectionId: conn.connectionId,
|
|
79
94
|
send: async (data) => {
|
|
80
|
-
const apigwManagementApi = new ApiGatewayManagementApiClient({
|
|
81
|
-
endpoint: process.env.WEBSOCKET_API_ENDPOINT,
|
|
82
|
-
});
|
|
83
|
-
|
|
84
95
|
try {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
96
|
+
await sender.send(
|
|
97
|
+
conn.connectionId,
|
|
98
|
+
data,
|
|
99
|
+
process.env.WEBSOCKET_API_ENDPOINT
|
|
100
|
+
);
|
|
90
101
|
} catch (error) {
|
|
91
|
-
if (error
|
|
102
|
+
if (error instanceof StaleConnectionError) {
|
|
92
103
|
console.log(
|
|
93
104
|
`Stale connection ${conn.connectionId}`
|
|
94
105
|
);
|
|
95
|
-
// Delete stale connection
|
|
96
106
|
await this.prisma.websocketConnection.deleteMany({
|
|
97
107
|
where: { connectionId: conn.connectionId },
|
|
98
108
|
});
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
const { prisma } = require('../../database/prisma');
|
|
2
2
|
const {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} = require('@aws-sdk/client-apigatewaymanagementapi');
|
|
3
|
+
StaleConnectionError,
|
|
4
|
+
} = require('../websocket-message-sender-interface');
|
|
6
5
|
const {
|
|
7
6
|
WebsocketConnectionRepositoryInterface,
|
|
8
7
|
} = require('./websocket-connection-repository-interface');
|
|
@@ -15,11 +14,17 @@ const {
|
|
|
15
14
|
* - Uses Int IDs with autoincrement
|
|
16
15
|
* - Requires ID conversion: String (app layer) ↔ Int (database)
|
|
17
16
|
* - All returned IDs are converted to strings for application layer consistency
|
|
17
|
+
*
|
|
18
|
+
* BREAKING CHANGE (v3): A messageSender must be explicitly provided for
|
|
19
|
+
* WebSocket send functionality. For AWS API Gateway, pass
|
|
20
|
+
* `new ApiGatewayMessageSender()` from @friggframework/provider-aws.
|
|
21
|
+
* See docs/architecture-decisions/010-decouple-aws-from-core.md for migration guide.
|
|
18
22
|
*/
|
|
19
23
|
class WebsocketConnectionRepositoryPostgres extends WebsocketConnectionRepositoryInterface {
|
|
20
|
-
constructor() {
|
|
24
|
+
constructor(messageSender = null) {
|
|
21
25
|
super();
|
|
22
26
|
this.prisma = prisma;
|
|
27
|
+
this._messageSender = messageSender;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
/**
|
|
@@ -108,25 +113,31 @@ class WebsocketConnectionRepositoryPostgres extends WebsocketConnectionRepositor
|
|
|
108
113
|
select: { connectionId: true },
|
|
109
114
|
});
|
|
110
115
|
|
|
116
|
+
if (!this._messageSender) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
'WebsocketConnectionRepositoryPostgres requires a messageSender for send functionality.\n' +
|
|
119
|
+
'Pass one via constructor, e.g.:\n' +
|
|
120
|
+
' const { ApiGatewayMessageSender } = require("@friggframework/provider-aws");\n' +
|
|
121
|
+
' new WebsocketConnectionRepositoryPostgres(new ApiGatewayMessageSender())\n' +
|
|
122
|
+
'See docs/architecture-decisions/010-decouple-aws-from-core.md for migration guide.'
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
const sender = this._messageSender;
|
|
126
|
+
|
|
111
127
|
return connections.map((conn) => ({
|
|
112
128
|
connectionId: conn.connectionId,
|
|
113
129
|
send: async (data) => {
|
|
114
|
-
const apigwManagementApi = new ApiGatewayManagementApiClient({
|
|
115
|
-
endpoint: process.env.WEBSOCKET_API_ENDPOINT,
|
|
116
|
-
});
|
|
117
|
-
|
|
118
130
|
try {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
131
|
+
await sender.send(
|
|
132
|
+
conn.connectionId,
|
|
133
|
+
data,
|
|
134
|
+
process.env.WEBSOCKET_API_ENDPOINT
|
|
135
|
+
);
|
|
124
136
|
} catch (error) {
|
|
125
|
-
if (error
|
|
137
|
+
if (error instanceof StaleConnectionError) {
|
|
126
138
|
console.log(
|
|
127
139
|
`Stale connection ${conn.connectionId}`
|
|
128
140
|
);
|
|
129
|
-
// Delete stale connection
|
|
130
141
|
await this.prisma.websocketConnection.deleteMany({
|
|
131
142
|
where: { connectionId: conn.connectionId },
|
|
132
143
|
});
|