@friggframework/core 2.0.0--canary.522.cbd3d5a.0 → 2.0.0--canary.517.35ee143.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 +67 -66
- package/CLAUDE.md +124 -148
- package/README.md +130 -165
- package/admin-scripts/index.js +16 -30
- package/admin-scripts/repositories/{admin-api-key-repository-documentdb.js → admin-process-repository-documentdb.js} +5 -5
- package/admin-scripts/repositories/{admin-api-key-repository-factory.js → admin-process-repository-factory.js} +15 -19
- 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-schedule-repository-factory.js +2 -6
- package/admin-scripts/repositories/script-schedule-repository-interface.js +14 -36
- package/admin-scripts/repositories/script-schedule-repository-mongo.js +13 -25
- package/admin-scripts/repositories/script-schedule-repository-postgres.js +13 -25
- package/application/commands/admin-script-commands.js +102 -266
- package/application/commands/integration-commands.js +6 -12
- package/application/index.js +3 -1
- package/assertions/get.js +4 -1
- package/associations/association.js +57 -59
- package/associations/model.js +41 -41
- package/core/CLAUDE.md +156 -222
- package/core/Worker.js +1 -5
- package/credential/repositories/credential-repository-documentdb.js +1 -71
- package/credential/repositories/credential-repository-interface.js +2 -8
- package/credential/repositories/credential-repository-mongo.js +4 -45
- package/credential/repositories/credential-repository-postgres.js +3 -44
- package/credential/repositories/credential-repository.js +4 -6
- package/credential/use-cases/get-credential-for-user.js +3 -21
- package/credential/use-cases/update-authentication-status.js +2 -5
- package/database/MONGODB_TRANSACTION_FIX.md +52 -72
- package/database/adapters/lambda-invoker.js +7 -8
- package/database/config.js +13 -15
- package/database/documentdb-encryption-service.js +13 -29
- package/database/documentdb-utils.js +12 -21
- package/database/encryption/README.md +36 -53
- package/database/encryption/encryption-schema-registry.js +9 -21
- package/database/encryption/field-encryption-service.js +4 -15
- package/database/encryption/logger.js +2 -3
- package/database/encryption/prisma-encryption-extension.js +8 -5
- package/database/index.js +2 -4
- package/database/models/IndividualUser.js +15 -15
- package/database/models/OrganizationUser.js +4 -6
- package/database/models/UserModel.js +2 -2
- package/database/models/WebsocketConnection.js +1 -4
- package/database/mongoose.js +2 -2
- package/database/prisma.js +14 -22
- package/database/repositories/health-check-repository-documentdb.js +7 -22
- package/database/repositories/health-check-repository-factory.js +3 -9
- package/database/repositories/health-check-repository-interface.js +5 -15
- package/database/repositories/health-check-repository-mongodb.js +6 -9
- package/database/repositories/health-check-repository-postgres.js +1 -1
- package/database/repositories/migration-status-repository-s3.js +5 -10
- package/database/use-cases/check-database-health-use-case.js +2 -3
- package/database/use-cases/check-database-state-use-case.js +7 -9
- package/database/use-cases/get-database-state-via-worker-use-case.js +12 -15
- package/database/use-cases/get-migration-status-use-case.js +2 -4
- package/database/use-cases/run-database-migration-use-case.js +9 -41
- package/database/use-cases/test-encryption-use-case.js +5 -4
- package/database/use-cases/trigger-database-migration-use-case.js +8 -9
- package/database/utils/mongodb-collection-utils.js +3 -11
- package/database/utils/mongodb-schema-init.js +4 -10
- package/database/utils/prisma-runner.js +74 -144
- package/database/utils/prisma-schema-parser.js +2 -2
- package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +80 -135
- package/encrypt/Cryptor.js +2 -8
- package/errors/fetch-error.js +1 -1
- package/generated/prisma-mongodb/edge.js +16 -59
- package/generated/prisma-mongodb/index-browser.js +12 -62
- package/generated/prisma-mongodb/index.d.ts +4850 -8136
- package/generated/prisma-mongodb/index.js +16 -59
- package/generated/prisma-mongodb/package.json +1 -1
- package/generated/prisma-mongodb/schema.prisma +33 -75
- package/generated/prisma-mongodb/wasm.js +16 -59
- package/generated/prisma-postgresql/edge.js +15 -59
- package/generated/prisma-postgresql/index-browser.js +11 -62
- package/generated/prisma-postgresql/index.d.ts +5963 -8930
- package/generated/prisma-postgresql/index.js +15 -59
- package/generated/prisma-postgresql/package.json +1 -1
- package/generated/prisma-postgresql/schema.prisma +33 -73
- package/generated/prisma-postgresql/wasm.js +15 -59
- package/handlers/WEBHOOKS.md +81 -104
- package/handlers/app-definition-loader.js +1 -1
- package/handlers/app-handler-helpers.js +2 -14
- package/handlers/backend-utils.js +1 -0
- package/handlers/database-migration-handler.js +45 -64
- package/handlers/middleware/admin-auth.js +53 -0
- package/handlers/routers/HEALTHCHECK.md +135 -170
- package/handlers/routers/auth.js +2 -23
- package/handlers/routers/db-migration.handler.js +3 -2
- package/handlers/routers/db-migration.js +33 -69
- package/handlers/routers/health.js +8 -23
- package/handlers/routers/integration-defined-routers.js +4 -4
- package/handlers/routers/integration-webhook-routers.js +7 -17
- package/handlers/routers/websocket.js +3 -9
- package/handlers/use-cases/check-integrations-health-use-case.js +6 -13
- package/handlers/workers/db-migration.js +26 -54
- package/index.js +9 -5
- package/integrations/WEBHOOK-QUICKSTART.md +14 -21
- package/integrations/index.js +0 -4
- package/integrations/integration-base.js +6 -9
- package/integrations/integration-router.js +146 -1817
- package/integrations/options.js +20 -47
- package/integrations/repositories/integration-repository-documentdb.js +24 -53
- package/integrations/repositories/integration-repository-factory.js +2 -6
- package/integrations/repositories/integration-repository-interface.js +9 -27
- package/integrations/repositories/integration-repository-postgres.js +3 -3
- package/integrations/repositories/process-repository-factory.js +1 -0
- package/integrations/repositories/process-repository-interface.js +5 -9
- package/integrations/repositories/process-repository-mongo.js +3 -7
- package/integrations/repositories/process-repository-postgres.js +2 -2
- package/integrations/tests/doubles/config-capturing-integration.js +8 -9
- package/integrations/tests/doubles/dummy-integration-class.js +8 -81
- package/integrations/tests/doubles/test-integration-repository.js +11 -51
- package/integrations/use-cases/create-integration.js +32 -36
- package/integrations/use-cases/create-process.js +9 -20
- package/integrations/use-cases/delete-integration-for-user.js +4 -5
- package/integrations/use-cases/get-integration-for-user.js +10 -25
- package/integrations/use-cases/get-integration-instance-by-definition.js +7 -13
- package/integrations/use-cases/get-integrations-for-user.js +1 -4
- package/integrations/use-cases/get-possible-integrations.js +1 -1
- package/integrations/use-cases/get-process.js +6 -5
- package/integrations/use-cases/index.js +1 -3
- package/integrations/use-cases/load-integration-context.js +5 -1
- package/integrations/use-cases/update-process-state.js +8 -10
- package/integrations/utils/map-integration-dto.js +3 -9
- package/jest-setup.js +1 -1
- package/jest-teardown.js +1 -1
- package/lambda/TimeoutCatcher.js +30 -32
- package/lambda/index.js +1 -1
- package/logs/index.js +3 -3
- package/modules/module.js +1 -3
- package/modules/repositories/module-repository-documentdb.js +48 -114
- package/modules/repositories/module-repository-interface.js +0 -13
- package/modules/repositories/module-repository-mongo.js +6 -46
- package/modules/repositories/module-repository-postgres.js +6 -48
- package/modules/repositories/module-repository.js +1 -34
- package/modules/requester/api-key.js +2 -0
- package/modules/requester/basic.js +1 -0
- package/modules/requester/oauth-2.js +2 -3
- package/modules/test/mock-api/definition.js +10 -15
- package/modules/test/mock-api/mocks/hubspot.js +36 -36
- package/modules/tests/doubles/test-module-factory.js +4 -10
- package/modules/tests/doubles/test-module-repository.js +1 -1
- package/modules/use-cases/get-entities-for-user.js +1 -1
- package/modules/use-cases/get-entity-options-by-id.js +6 -8
- package/modules/use-cases/get-module.js +9 -12
- package/modules/use-cases/process-authorization-callback.js +6 -12
- package/modules/use-cases/refresh-entity-options.js +6 -8
- package/modules/use-cases/test-module-auth.js +6 -8
- package/modules/utils/map-module-dto.js +2 -2
- package/package.json +5 -7
- package/prisma-mongodb/schema.prisma +33 -75
- package/prisma-postgresql/schema.prisma +33 -73
- package/queues/queuer-util.js +1 -5
- package/syncs/model.js +51 -51
- package/syncs/repositories/sync-repository-documentdb.js +19 -39
- package/syncs/repositories/sync-repository-interface.js +2 -6
- package/syncs/repositories/sync-repository-postgres.js +13 -19
- package/syncs/sync.js +100 -104
- package/token/repositories/token-repository-factory.js +3 -1
- package/types/assertions/index.d.ts +72 -72
- package/types/associations/index.d.ts +57 -57
- package/types/core/index.d.ts +53 -59
- package/types/database/index.d.ts +2 -2
- package/types/encrypt/index.d.ts +3 -3
- package/types/errors/index.d.ts +54 -61
- package/types/eslint-config/index.d.ts +40 -40
- package/types/integrations/index.d.ts +183 -185
- package/types/lambda/index.d.ts +26 -26
- package/types/logs/index.d.ts +4 -4
- package/types/module-plugin/index.d.ts +237 -239
- package/types/prettier-config/index.d.ts +5 -5
- package/types/syncs/index.d.ts +108 -102
- package/types/test-environment/index.d.ts +14 -14
- package/types/tsconfig.json +93 -95
- package/user/tests/doubles/test-user-repository.js +2 -5
- package/user/use-cases/authenticate-user.js +4 -2
- package/user/use-cases/authenticate-with-shared-secret.js +1 -1
- package/user/use-cases/create-individual-user.js +7 -8
- 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/get-user-from-adopter-jwt.js +2 -0
- package/user/use-cases/get-user-from-bearer-token.js +3 -7
- package/user/use-cases/get-user-from-x-frigg-headers.js +11 -14
- package/user/use-cases/login-user.js +8 -8
- package/user/user.js +4 -18
- package/utils/backend-path.js +2 -2
- package/utils/index.js +1 -4
- package/websocket/repositories/websocket-connection-repository-documentdb.js +8 -21
- package/websocket/repositories/websocket-connection-repository-mongo.js +4 -8
- package/websocket/repositories/websocket-connection-repository-postgres.js +4 -8
- package/websocket/repositories/websocket-connection-repository.js +4 -8
- package/admin-scripts/repositories/admin-api-key-repository-interface.js +0 -121
- package/admin-scripts/repositories/admin-api-key-repository-mongo.js +0 -155
- package/admin-scripts/repositories/admin-api-key-repository-postgres.js +0 -189
- package/admin-scripts/repositories/script-execution-repository-documentdb.js +0 -21
- package/admin-scripts/repositories/script-execution-repository-factory.js +0 -55
- package/admin-scripts/repositories/script-execution-repository-interface.js +0 -193
- package/admin-scripts/repositories/script-execution-repository-mongo.js +0 -278
- package/admin-scripts/repositories/script-execution-repository-postgres.js +0 -320
- package/credential/credential-router.js +0 -262
- package/credential/use-cases/delete-credential-for-user.js +0 -50
- package/credential/use-cases/list-credentials-for-user.js +0 -31
- package/credential/use-cases/reauthorize-credential.js +0 -103
- package/handlers/routers/admin.js +0 -450
- package/handlers/routers/docs.js +0 -180
- package/handlers/routers/middleware/loadUser.js +0 -39
- package/handlers/routers/middleware/requireAdmin.js +0 -41
- package/handlers/routers/middleware/requireLoggedInUser.js +0 -19
- package/integrations/use-cases/execute-proxy-request.js +0 -564
- package/modules/domain/entities/AuthorizationSession.js +0 -131
- package/modules/domain/entities/index.js +0 -10
- package/modules/repositories/authorization-session-repository-factory.js +0 -55
- package/modules/repositories/authorization-session-repository-interface.js +0 -75
- package/modules/repositories/authorization-session-repository-mongo.js +0 -169
- package/modules/repositories/authorization-session-repository-postgres.js +0 -170
- package/modules/use-cases/delete-module-entity.js +0 -23
- package/modules/use-cases/get-authorization-requirements.js +0 -110
- package/modules/use-cases/get-module-entity-by-id.js +0 -16
- package/modules/use-cases/process-authorization-step.js +0 -160
- package/modules/use-cases/start-authorization-session.js +0 -86
- package/modules/use-cases/update-module-entity.js +0 -27
- package/openapi/openapi-spec-generator.js +0 -370
- package/openapi/openapi-v1.yaml +0 -1360
- package/openapi/openapi-v2.yaml +0 -1535
- package/user/use-cases/delete-user.js +0 -47
package/core/Worker.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
const {
|
|
2
|
-
SQSClient,
|
|
3
|
-
GetQueueUrlCommand,
|
|
4
|
-
SendMessageCommand,
|
|
5
|
-
} = require('@aws-sdk/client-sqs');
|
|
1
|
+
const { SQSClient, GetQueueUrlCommand, SendMessageCommand } = require('@aws-sdk/client-sqs');
|
|
6
2
|
const _ = require('lodash');
|
|
7
3
|
const { RequiredPropertyError } = require('../errors');
|
|
8
4
|
const { get } = require('../assertions');
|
|
@@ -3,7 +3,6 @@ const {
|
|
|
3
3
|
toObjectId,
|
|
4
4
|
fromObjectId,
|
|
5
5
|
findOne,
|
|
6
|
-
findMany,
|
|
7
6
|
insertOne,
|
|
8
7
|
updateOne,
|
|
9
8
|
deleteOne,
|
|
@@ -107,10 +106,7 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
|
|
|
107
106
|
const updateDocument = {
|
|
108
107
|
userId: existing.userId,
|
|
109
108
|
externalId: existing.externalId,
|
|
110
|
-
authIsValid:
|
|
111
|
-
authIsValid !== undefined
|
|
112
|
-
? authIsValid
|
|
113
|
-
: existing.authIsValid,
|
|
109
|
+
authIsValid: authIsValid !== undefined ? authIsValid : existing.authIsValid,
|
|
114
110
|
data: mergedData,
|
|
115
111
|
updatedAt: now,
|
|
116
112
|
};
|
|
@@ -176,50 +172,8 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
|
|
|
176
172
|
return this._mapCredential(decryptedCredential);
|
|
177
173
|
}
|
|
178
174
|
|
|
179
|
-
/**
|
|
180
|
-
* Find credential(s) by filter criteria
|
|
181
|
-
*
|
|
182
|
-
* When filter includes only userId, returns an array of all credentials for that user
|
|
183
|
-
* When filter includes credentialId or externalId, returns a single credential or null
|
|
184
|
-
*
|
|
185
|
-
* @param {Object} filter
|
|
186
|
-
* @param {string} [filter.userId] - User ID
|
|
187
|
-
* @param {string} [filter.externalId] - External ID
|
|
188
|
-
* @param {string} [filter.credentialId] - Credential ID
|
|
189
|
-
* @returns {Promise<Array|Object|null>} Credential array, single credential, or null
|
|
190
|
-
*/
|
|
191
175
|
async findCredential(filter) {
|
|
192
176
|
const query = this._buildFilter(filter);
|
|
193
|
-
|
|
194
|
-
// If filtering by userId only, return all credentials for that user
|
|
195
|
-
const hasOnlyUserId =
|
|
196
|
-
filter.userId &&
|
|
197
|
-
!filter.credentialId &&
|
|
198
|
-
!filter.externalId &&
|
|
199
|
-
!filter.id;
|
|
200
|
-
|
|
201
|
-
if (hasOnlyUserId) {
|
|
202
|
-
const credentials = await findMany(
|
|
203
|
-
this.prisma,
|
|
204
|
-
'Credential',
|
|
205
|
-
query
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
const decryptedCredentials = await Promise.all(
|
|
209
|
-
credentials.map(async (credential) => {
|
|
210
|
-
const decrypted =
|
|
211
|
-
await this.encryptionService.decryptFields(
|
|
212
|
-
'Credential',
|
|
213
|
-
credential
|
|
214
|
-
);
|
|
215
|
-
return this._mapCredentialWithMetadata(decrypted);
|
|
216
|
-
})
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
return decryptedCredentials;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Otherwise, find single credential
|
|
223
177
|
const credential = await findOne(this.prisma, 'Credential', query);
|
|
224
178
|
if (!credential) return null;
|
|
225
179
|
|
|
@@ -345,30 +299,6 @@ class CredentialRepositoryDocumentDB extends CredentialRepositoryInterface {
|
|
|
345
299
|
...data,
|
|
346
300
|
};
|
|
347
301
|
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* Map credential document with metadata (for list views)
|
|
351
|
-
* Includes timestamps and additional fields needed by API
|
|
352
|
-
* @private
|
|
353
|
-
*/
|
|
354
|
-
_mapCredentialWithMetadata(doc) {
|
|
355
|
-
const data = doc?.data || {};
|
|
356
|
-
const id = fromObjectId(doc?._id);
|
|
357
|
-
const userId = doc?.userId;
|
|
358
|
-
return {
|
|
359
|
-
id,
|
|
360
|
-
type: doc?.type,
|
|
361
|
-
userId,
|
|
362
|
-
externalId: doc?.externalId ?? null,
|
|
363
|
-
authIsValid: doc?.authIsValid ?? null,
|
|
364
|
-
entityCount: doc?.entityCount,
|
|
365
|
-
createdAt: doc?.createdAt,
|
|
366
|
-
updatedAt: doc?.updatedAt,
|
|
367
|
-
access_token: data.access_token,
|
|
368
|
-
refresh_token: data.refresh_token,
|
|
369
|
-
...data,
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
302
|
}
|
|
373
303
|
|
|
374
304
|
module.exports = { CredentialRepositoryDocumentDB };
|
|
@@ -68,16 +68,10 @@ class CredentialRepositoryInterface {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
* Find credential
|
|
72
|
-
*
|
|
73
|
-
* When filter includes only userId, returns an array of all credentials for that user
|
|
74
|
-
* When filter includes credentialId or externalId, returns a single credential or null
|
|
71
|
+
* Find a credential by filter criteria
|
|
75
72
|
*
|
|
76
73
|
* @param {Object} filter - Filter criteria
|
|
77
|
-
* @
|
|
78
|
-
* @param {string} [filter.externalId] - External ID
|
|
79
|
-
* @param {string} [filter.credentialId] - Credential ID
|
|
80
|
-
* @returns {Promise<Array|Object|null>} Credential array, single credential, or null
|
|
74
|
+
* @returns {Promise<Object|null>} Credential object or null if not found
|
|
81
75
|
* @abstract
|
|
82
76
|
*/
|
|
83
77
|
async findCredential(filter) {
|
|
@@ -121,10 +121,7 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
|
|
|
121
121
|
data: {
|
|
122
122
|
userId: existing.userId,
|
|
123
123
|
externalId: existing.externalId,
|
|
124
|
-
authIsValid:
|
|
125
|
-
authIsValid !== undefined
|
|
126
|
-
? authIsValid
|
|
127
|
-
: existing.authIsValid,
|
|
124
|
+
authIsValid: authIsValid !== undefined ? authIsValid : existing.authIsValid,
|
|
128
125
|
data: mergedData,
|
|
129
126
|
},
|
|
130
127
|
});
|
|
@@ -157,52 +154,18 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
|
|
|
157
154
|
}
|
|
158
155
|
|
|
159
156
|
/**
|
|
160
|
-
* Find credential
|
|
161
|
-
* Replaces: Credential.
|
|
162
|
-
*
|
|
163
|
-
* When filter includes only userId, returns an array of all credentials for that user
|
|
164
|
-
* When filter includes credentialId or externalId, returns a single credential or null
|
|
157
|
+
* Find a credential by filter criteria
|
|
158
|
+
* Replaces: Credential.findOne(query)
|
|
165
159
|
*
|
|
166
160
|
* @param {Object} filter
|
|
167
161
|
* @param {string} [filter.userId] - User ID
|
|
168
162
|
* @param {string} [filter.externalId] - External ID
|
|
169
163
|
* @param {string} [filter.credentialId] - Credential ID
|
|
170
|
-
* @returns {Promise<
|
|
164
|
+
* @returns {Promise<Object|null>} Credential object or null if not found
|
|
171
165
|
*/
|
|
172
166
|
async findCredential(filter) {
|
|
173
167
|
const where = this._convertFilterToWhere(filter);
|
|
174
168
|
|
|
175
|
-
// If filtering by userId only, return all credentials for that user
|
|
176
|
-
const hasOnlyUserId =
|
|
177
|
-
filter.userId &&
|
|
178
|
-
!filter.credentialId &&
|
|
179
|
-
!filter.externalId &&
|
|
180
|
-
!filter.id;
|
|
181
|
-
|
|
182
|
-
if (hasOnlyUserId) {
|
|
183
|
-
const credentials = await this.prisma.credential.findMany({
|
|
184
|
-
where,
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
return credentials.map((credential) => {
|
|
188
|
-
const data = credential.data || {};
|
|
189
|
-
return {
|
|
190
|
-
id: credential.id,
|
|
191
|
-
type: credential.type,
|
|
192
|
-
userId: credential.userId,
|
|
193
|
-
externalId: credential.externalId,
|
|
194
|
-
authIsValid: credential.authIsValid,
|
|
195
|
-
entityCount: credential.entityCount,
|
|
196
|
-
createdAt: credential.createdAt,
|
|
197
|
-
updatedAt: credential.updatedAt,
|
|
198
|
-
access_token: data.access_token,
|
|
199
|
-
refresh_token: data.refresh_token,
|
|
200
|
-
...data,
|
|
201
|
-
};
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Otherwise, find single credential
|
|
206
169
|
const credential = await this.prisma.credential.findFirst({
|
|
207
170
|
where,
|
|
208
171
|
});
|
|
@@ -215,13 +178,9 @@ class CredentialRepositoryMongo extends CredentialRepositoryInterface {
|
|
|
215
178
|
|
|
216
179
|
return {
|
|
217
180
|
id: credential.id,
|
|
218
|
-
type: credential.type,
|
|
219
181
|
userId: credential.userId,
|
|
220
182
|
externalId: credential.externalId,
|
|
221
183
|
authIsValid: credential.authIsValid,
|
|
222
|
-
entityCount: credential.entityCount,
|
|
223
|
-
createdAt: credential.createdAt,
|
|
224
|
-
updatedAt: credential.updatedAt,
|
|
225
184
|
access_token: data.access_token,
|
|
226
185
|
refresh_token: data.refresh_token,
|
|
227
186
|
...data,
|
|
@@ -139,10 +139,7 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
|
|
|
139
139
|
data: {
|
|
140
140
|
userId: this._convertId(existing.userId),
|
|
141
141
|
externalId: existing.externalId,
|
|
142
|
-
authIsValid:
|
|
143
|
-
authIsValid !== undefined
|
|
144
|
-
? authIsValid
|
|
145
|
-
: existing.authIsValid,
|
|
142
|
+
authIsValid: authIsValid !== undefined ? authIsValid : existing.authIsValid,
|
|
146
143
|
data: mergedData,
|
|
147
144
|
},
|
|
148
145
|
});
|
|
@@ -176,51 +173,17 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
|
|
|
176
173
|
}
|
|
177
174
|
|
|
178
175
|
/**
|
|
179
|
-
* Find credential
|
|
180
|
-
*
|
|
181
|
-
* When filter includes only userId, returns an array of all credentials for that user
|
|
182
|
-
* When filter includes credentialId or externalId, returns a single credential or null
|
|
176
|
+
* Find a credential by filter criteria
|
|
183
177
|
*
|
|
184
178
|
* @param {Object} filter
|
|
185
179
|
* @param {string} [filter.userId] - User ID (string from application layer)
|
|
186
180
|
* @param {string} [filter.externalId] - External ID
|
|
187
181
|
* @param {string} [filter.credentialId] - Credential ID (string from application layer)
|
|
188
|
-
* @returns {Promise<
|
|
182
|
+
* @returns {Promise<Object|null>} Credential object with string IDs or null if not found
|
|
189
183
|
*/
|
|
190
184
|
async findCredential(filter) {
|
|
191
185
|
const where = this._convertFilterToWhere(filter);
|
|
192
186
|
|
|
193
|
-
// If filtering by userId only, return all credentials for that user
|
|
194
|
-
const hasOnlyUserId =
|
|
195
|
-
filter.userId &&
|
|
196
|
-
!filter.credentialId &&
|
|
197
|
-
!filter.externalId &&
|
|
198
|
-
!filter.id;
|
|
199
|
-
|
|
200
|
-
if (hasOnlyUserId) {
|
|
201
|
-
const credentials = await this.prisma.credential.findMany({
|
|
202
|
-
where,
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
return credentials.map((credential) => {
|
|
206
|
-
const data = credential.data || {};
|
|
207
|
-
return {
|
|
208
|
-
id: credential.id.toString(),
|
|
209
|
-
type: credential.type,
|
|
210
|
-
userId: credential.userId?.toString(),
|
|
211
|
-
externalId: credential.externalId,
|
|
212
|
-
authIsValid: credential.authIsValid,
|
|
213
|
-
entityCount: credential.entityCount,
|
|
214
|
-
createdAt: credential.createdAt,
|
|
215
|
-
updatedAt: credential.updatedAt,
|
|
216
|
-
access_token: data.access_token,
|
|
217
|
-
refresh_token: data.refresh_token,
|
|
218
|
-
...data,
|
|
219
|
-
};
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Otherwise, find single credential
|
|
224
187
|
const credential = await this.prisma.credential.findFirst({
|
|
225
188
|
where,
|
|
226
189
|
});
|
|
@@ -233,13 +196,9 @@ class CredentialRepositoryPostgres extends CredentialRepositoryInterface {
|
|
|
233
196
|
|
|
234
197
|
return {
|
|
235
198
|
id: credential.id.toString(),
|
|
236
|
-
type: credential.type,
|
|
237
199
|
userId: credential.userId?.toString(),
|
|
238
200
|
externalId: credential.externalId,
|
|
239
201
|
authIsValid: credential.authIsValid,
|
|
240
|
-
entityCount: credential.entityCount,
|
|
241
|
-
createdAt: credential.createdAt,
|
|
242
|
-
updatedAt: credential.updatedAt,
|
|
243
202
|
access_token: data.access_token,
|
|
244
203
|
refresh_token: data.refresh_token,
|
|
245
204
|
...data,
|
|
@@ -114,7 +114,7 @@ class CredentialRepository extends CredentialRepositoryInterface {
|
|
|
114
114
|
userId,
|
|
115
115
|
externalId,
|
|
116
116
|
authIsValid,
|
|
117
|
-
|
|
117
|
+
|
|
118
118
|
...oauthData
|
|
119
119
|
} = details;
|
|
120
120
|
|
|
@@ -156,7 +156,7 @@ class CredentialRepository extends CredentialRepositoryInterface {
|
|
|
156
156
|
userId: userId || user,
|
|
157
157
|
externalId,
|
|
158
158
|
authIsValid: authIsValid,
|
|
159
|
-
|
|
159
|
+
|
|
160
160
|
data: oauthData,
|
|
161
161
|
},
|
|
162
162
|
});
|
|
@@ -229,7 +229,7 @@ class CredentialRepository extends CredentialRepositoryInterface {
|
|
|
229
229
|
userId,
|
|
230
230
|
externalId,
|
|
231
231
|
authIsValid,
|
|
232
|
-
|
|
232
|
+
|
|
233
233
|
...oauthData
|
|
234
234
|
} = updates;
|
|
235
235
|
|
|
@@ -243,9 +243,7 @@ class CredentialRepository extends CredentialRepositoryInterface {
|
|
|
243
243
|
externalId:
|
|
244
244
|
externalId !== undefined ? externalId : existing.externalId,
|
|
245
245
|
authIsValid:
|
|
246
|
-
authIsValid !== undefined
|
|
247
|
-
? authIsValid
|
|
248
|
-
: existing.authIsValid,
|
|
246
|
+
authIsValid !== undefined ? authIsValid : existing.authIsValid,
|
|
249
247
|
data: mergedData,
|
|
250
248
|
},
|
|
251
249
|
});
|
|
@@ -1,38 +1,20 @@
|
|
|
1
|
-
const Boom = require('@hapi/boom');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Get Credential For User Use Case
|
|
5
|
-
* Retrieves a single credential after verifying ownership
|
|
6
|
-
*
|
|
7
|
-
* Business Logic:
|
|
8
|
-
* - Verify credential exists
|
|
9
|
-
* - Verify credential belongs to user (authorization)
|
|
10
|
-
* - Return credential
|
|
11
|
-
*/
|
|
12
1
|
class GetCredentialForUser {
|
|
13
2
|
constructor({ credentialRepository }) {
|
|
14
3
|
this.credentialRepository = credentialRepository;
|
|
15
4
|
}
|
|
16
5
|
|
|
17
|
-
/**
|
|
18
|
-
* Execute the use case
|
|
19
|
-
* @param {string} credentialId - Credential ID
|
|
20
|
-
* @param {string} userId - User ID (for ownership verification)
|
|
21
|
-
* @returns {Promise<Object>} Credential object
|
|
22
|
-
*/
|
|
23
6
|
async execute(credentialId, userId) {
|
|
24
7
|
const credential = await this.credentialRepository.findCredentialById(
|
|
25
8
|
credentialId
|
|
26
9
|
);
|
|
27
10
|
|
|
28
11
|
if (!credential) {
|
|
29
|
-
throw
|
|
12
|
+
throw new Error(`Credential with id ${credentialId} not found`);
|
|
30
13
|
}
|
|
31
14
|
|
|
32
|
-
// Verify ownership - compare as strings to handle both MongoDB and PostgreSQL
|
|
33
15
|
if (credential.userId.toString() !== userId.toString()) {
|
|
34
|
-
throw
|
|
35
|
-
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Credential ${credentialId} does not belong to user ${userId}`
|
|
36
18
|
);
|
|
37
19
|
}
|
|
38
20
|
|
|
@@ -8,11 +8,8 @@ class UpdateAuthenticationStatus {
|
|
|
8
8
|
* @param {boolean} authIsValid
|
|
9
9
|
*/
|
|
10
10
|
async execute(credentialId, authIsValid) {
|
|
11
|
-
await this.credentialRepository.updateAuthenticationStatus(
|
|
12
|
-
credentialId,
|
|
13
|
-
authIsValid
|
|
14
|
-
);
|
|
11
|
+
await this.credentialRepository.updateAuthenticationStatus(credentialId, authIsValid);
|
|
15
12
|
}
|
|
16
13
|
}
|
|
17
14
|
|
|
18
|
-
module.exports = { UpdateAuthenticationStatus };
|
|
15
|
+
module.exports = { UpdateAuthenticationStatus };
|
|
@@ -15,9 +15,9 @@ MongoDB does not allow creating collections (namespaces) inside multi-document t
|
|
|
15
15
|
|
|
16
16
|
### Technical Details
|
|
17
17
|
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
18
|
+
- **MongoDB Constraint**: Collections must exist before being used in multi-document transactions
|
|
19
|
+
- **Prisma Behavior**: Prisma may implicitly use transactions for certain operations
|
|
20
|
+
- **Impact**: Health checks fail on fresh databases or when collections haven't been created yet
|
|
21
21
|
|
|
22
22
|
## Solution
|
|
23
23
|
|
|
@@ -37,34 +37,30 @@ This follows the **"fail fast"** principle and ensures consistent state across a
|
|
|
37
37
|
### Changes Made
|
|
38
38
|
|
|
39
39
|
1. **Created MongoDB Schema Initialization** (`packages/core/database/utils/mongodb-schema-init.js`)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
- Fails fast if database not connected
|
|
40
|
+
- `initializeMongoDBSchema()` - Ensures all Prisma collections exist at startup
|
|
41
|
+
- `getPrismaCollections()` - Returns list of all Prisma collection names
|
|
42
|
+
- `PRISMA_COLLECTIONS` - Constant array of all 13 Prisma collections
|
|
43
|
+
- Only runs for MongoDB (skips PostgreSQL)
|
|
44
|
+
- Fails fast if database not connected
|
|
46
45
|
|
|
47
46
|
2. **Created MongoDB Collection Utilities** (`packages/core/database/utils/mongodb-collection-utils.js`)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
- Handles race conditions gracefully (NamespaceExists errors)
|
|
47
|
+
- `ensureCollectionExists(collectionName)` - Ensures a single collection exists
|
|
48
|
+
- `ensureCollectionsExist(collectionNames)` - Batch creates multiple collections
|
|
49
|
+
- `collectionExists(collectionName)` - Checks if a collection exists
|
|
50
|
+
- Handles race conditions gracefully (NamespaceExists errors)
|
|
53
51
|
|
|
54
52
|
3. **Integrated into Database Connection** (`packages/core/database/prisma.js`)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
- Ensures all collections exist before application handles requests
|
|
53
|
+
- Modified `connectPrisma()` to call `initializeMongoDBSchema()` after connection
|
|
54
|
+
- Ensures all collections exist before application handles requests
|
|
58
55
|
|
|
59
56
|
4. **Updated Health Check Repository** (`packages/core/database/repositories/health-check-repository-mongodb.js`)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
- Added documentation noting schema is initialized at startup
|
|
57
|
+
- Removed per-operation collection existence checks
|
|
58
|
+
- Added documentation noting schema is initialized at startup
|
|
63
59
|
|
|
64
60
|
5. **Added Comprehensive Tests**
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
- `mongodb-schema-init.test.js` - Tests schema initialization system
|
|
62
|
+
- `mongodb-collection-utils.test.js` - Tests collection utility functions
|
|
63
|
+
- Tests error handling, race conditions, and edge cases
|
|
68
64
|
|
|
69
65
|
### Implementation Flow
|
|
70
66
|
|
|
@@ -97,26 +93,23 @@ await prisma.credential.create({ data: {...} }); // Works without namespace erro
|
|
|
97
93
|
## Benefits
|
|
98
94
|
|
|
99
95
|
### Immediate Benefits
|
|
100
|
-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
-
- ✅ Idempotent - safe to run multiple times and across multiple instances
|
|
96
|
+
- ✅ Fixes encryption health check failures on fresh databases
|
|
97
|
+
- ✅ Prevents transaction namespace errors across **all** Prisma operations
|
|
98
|
+
- ✅ No per-operation overhead - collections created once at startup
|
|
99
|
+
- ✅ Fail fast - database issues discovered immediately at startup
|
|
100
|
+
- ✅ Idempotent - safe to run multiple times and across multiple instances
|
|
106
101
|
|
|
107
102
|
### Architectural Benefits
|
|
108
|
-
|
|
109
|
-
-
|
|
110
|
-
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
- ✅ **Well-tested and documented**: Comprehensive test coverage and documentation
|
|
103
|
+
- ✅ **Clean separation of concerns**: Schema initialization is infrastructure concern, handled at startup
|
|
104
|
+
- ✅ **Follows DDD/Hexagonal Architecture**: Infrastructure layer handles database setup, repositories focus on business operations
|
|
105
|
+
- ✅ **Consistent across all environments**: Dev, test, staging, production all follow same pattern
|
|
106
|
+
- ✅ **No repository-level checks needed**: All repositories benefit automatically
|
|
107
|
+
- ✅ **Well-tested and documented**: Comprehensive test coverage and documentation
|
|
114
108
|
|
|
115
109
|
### Operational Benefits
|
|
116
|
-
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
- ✅ **Production-ready**: Handles race conditions, errors, and edge cases gracefully
|
|
110
|
+
- ✅ **Predictable startup**: Clear logging of schema initialization
|
|
111
|
+
- ✅ **Zero runtime overhead**: Collections created once, not on every operation
|
|
112
|
+
- ✅ **Production-ready**: Handles race conditions, errors, and edge cases gracefully
|
|
120
113
|
|
|
121
114
|
## Design Decisions
|
|
122
115
|
|
|
@@ -125,19 +118,16 @@ await prisma.credential.create({ data: {...} }); // Works without namespace erro
|
|
|
125
118
|
We considered two approaches:
|
|
126
119
|
|
|
127
120
|
**❌ Per-Operation Checks (Initial approach)**
|
|
128
|
-
|
|
129
121
|
```javascript
|
|
130
122
|
async createCredential(data) {
|
|
131
123
|
await ensureCollectionExists('Credential'); // Check every time
|
|
132
124
|
return await prisma.credential.create({ data });
|
|
133
125
|
}
|
|
134
126
|
```
|
|
135
|
-
|
|
136
|
-
-
|
|
137
|
-
- Cons: Runtime overhead, repeated checks, scattered logic
|
|
127
|
+
- Pros: Guarantees collection exists before each operation
|
|
128
|
+
- Cons: Runtime overhead, repeated checks, scattered logic
|
|
138
129
|
|
|
139
130
|
**✅ Startup Initialization (Final approach)**
|
|
140
|
-
|
|
141
131
|
```javascript
|
|
142
132
|
// Once at startup
|
|
143
133
|
await connectPrisma(); // Initializes all collections
|
|
@@ -147,9 +137,8 @@ async createCredential(data) {
|
|
|
147
137
|
return await prisma.credential.create({ data }); // No checks needed
|
|
148
138
|
}
|
|
149
139
|
```
|
|
150
|
-
|
|
151
|
-
-
|
|
152
|
-
- Cons: Requires database connection at startup (already required)
|
|
140
|
+
- Pros: Zero runtime overhead, centralized logic, fail fast, consistent
|
|
141
|
+
- Cons: Requires database connection at startup (already required)
|
|
153
142
|
|
|
154
143
|
### Benefits of Startup Approach
|
|
155
144
|
|
|
@@ -170,7 +159,6 @@ MongoDB schema initialization complete - 13 collections verified (45ms)
|
|
|
170
159
|
```
|
|
171
160
|
|
|
172
161
|
On subsequent startups (collections already exist):
|
|
173
|
-
|
|
174
162
|
```
|
|
175
163
|
Initializing MongoDB schema - ensuring all collections exist...
|
|
176
164
|
MongoDB schema initialization complete - 13 collections verified (12ms)
|
|
@@ -178,41 +166,33 @@ MongoDB schema initialization complete - 13 collections verified (12ms)
|
|
|
178
166
|
|
|
179
167
|
## References
|
|
180
168
|
|
|
181
|
-
-
|
|
182
|
-
-
|
|
183
|
-
-
|
|
184
|
-
-
|
|
169
|
+
- [Prisma Issue #8305](https://github.com/prisma/prisma/issues/8305) - MongoDB "Cannot create namespace" error
|
|
170
|
+
- [Mongoose Issue #6699](https://github.com/Automattic/mongoose/issues/6699) - Similar issue in Mongoose
|
|
171
|
+
- [MongoDB Transactions Documentation](https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-operations) - Operations allowed in transactions
|
|
172
|
+
- [Prisma MongoDB Guide](https://www.prisma.io/docs/guides/database/mongodb) - Using Prisma with MongoDB
|
|
185
173
|
|
|
186
174
|
## Future Considerations
|
|
187
175
|
|
|
188
176
|
### Automatic Schema Sync
|
|
189
|
-
|
|
190
177
|
Consider enhancing the system to:
|
|
191
|
-
|
|
192
|
-
-
|
|
193
|
-
-
|
|
194
|
-
- Provide CLI command for manual schema initialization
|
|
178
|
+
- Parse Prisma schema file dynamically to extract collection names
|
|
179
|
+
- Auto-detect schema changes and create new collections
|
|
180
|
+
- Provide CLI command for manual schema initialization
|
|
195
181
|
|
|
196
182
|
### Migration Support
|
|
197
|
-
|
|
198
183
|
For production deployments with existing data:
|
|
199
|
-
|
|
200
|
-
-
|
|
201
|
-
-
|
|
202
|
-
- Add health check for schema initialization status
|
|
184
|
+
- Document migration procedures for new collections
|
|
185
|
+
- Consider pre-migration scripts for blue-green deployments
|
|
186
|
+
- Add health check for schema initialization status
|
|
203
187
|
|
|
204
188
|
### Multi-Database Support
|
|
205
|
-
|
|
206
189
|
The system already handles:
|
|
207
|
-
|
|
208
|
-
-
|
|
209
|
-
-
|
|
210
|
-
- Consider adding explicit migration support for DocumentDB-specific features
|
|
190
|
+
- ✅ MongoDB - Full schema initialization
|
|
191
|
+
- ✅ PostgreSQL - Skips initialization (uses Prisma migrations)
|
|
192
|
+
- Consider adding explicit migration support for DocumentDB-specific features
|
|
211
193
|
|
|
212
194
|
### Index Creation
|
|
213
|
-
|
|
214
195
|
Future enhancement could also create indexes at startup:
|
|
215
|
-
|
|
216
|
-
-
|
|
217
|
-
-
|
|
218
|
-
- Provide index health checks
|
|
196
|
+
- Parse Prisma schema for `@@index` directives
|
|
197
|
+
- Create indexes if they don't exist
|
|
198
|
+
- Provide index health checks
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Lambda Invoker Adapter
|
|
3
3
|
* Infrastructure layer - handles AWS Lambda function invocations
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* Part of Hexagonal Architecture:
|
|
6
6
|
* - Infrastructure Layer adapter for AWS SDK
|
|
7
7
|
* - Used by Domain Layer use cases
|
|
@@ -25,7 +25,7 @@ class LambdaInvocationError extends Error {
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Adapter for invoking AWS Lambda functions
|
|
28
|
-
*
|
|
28
|
+
*
|
|
29
29
|
* Infrastructure layer - handles AWS SDK communication
|
|
30
30
|
* Converts AWS SDK responses to domain-friendly formats
|
|
31
31
|
*/
|
|
@@ -39,7 +39,7 @@ class LambdaInvoker {
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* Invoke Lambda function synchronously
|
|
42
|
-
*
|
|
42
|
+
*
|
|
43
43
|
* @param {string} functionName - Lambda function name or ARN
|
|
44
44
|
* @param {Object} payload - Event payload to send to Lambda
|
|
45
45
|
* @returns {Promise<Object>} Parsed response body
|
|
@@ -74,8 +74,7 @@ class LambdaInvoker {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// Lambda returned error status
|
|
77
|
-
const errorMessage =
|
|
78
|
-
result.body?.error || 'Lambda invocation failed';
|
|
77
|
+
const errorMessage = result.body?.error || 'Lambda invocation failed';
|
|
79
78
|
throw new LambdaInvocationError(
|
|
80
79
|
`Lambda ${functionName} returned error: ${errorMessage}`,
|
|
81
80
|
functionName,
|
|
@@ -88,11 +87,11 @@ class LambdaInvoker {
|
|
|
88
87
|
}
|
|
89
88
|
|
|
90
89
|
// Wrap AWS SDK errors
|
|
91
|
-
throw new Error(
|
|
92
|
-
`Failed to invoke Lambda ${functionName}: ${error.message}`
|
|
93
|
-
);
|
|
90
|
+
throw new Error(`Failed to invoke Lambda ${functionName}: ${error.message}`);
|
|
94
91
|
}
|
|
95
92
|
}
|
|
96
93
|
}
|
|
97
94
|
|
|
98
95
|
module.exports = { LambdaInvoker, LambdaInvocationError };
|
|
96
|
+
|
|
97
|
+
|