@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
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Scripts Module
|
|
3
|
+
*
|
|
4
|
+
* Exports repository interfaces and factories for admin script management.
|
|
5
|
+
* Concrete implementations support MongoDB, PostgreSQL, and DocumentDB.
|
|
6
|
+
*
|
|
7
|
+
* Repository interfaces follow the Port pattern in Hexagonal Architecture:
|
|
8
|
+
* - Define contracts for data access
|
|
9
|
+
* - Enable dependency injection
|
|
10
|
+
* - Allow testing with mocks
|
|
11
|
+
* - Support multiple database implementations
|
|
12
|
+
*
|
|
13
|
+
* Authentication:
|
|
14
|
+
* - Uses ENV-based ADMIN_API_KEY (see handlers/middleware/admin-auth.js)
|
|
15
|
+
* - No database-backed API keys (simplified from original design)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// Repository Interfaces
|
|
19
|
+
const { AdminProcessRepositoryInterface } = require('./repositories/admin-process-repository-interface');
|
|
20
|
+
const { ScriptScheduleRepositoryInterface } = require('./repositories/script-schedule-repository-interface');
|
|
21
|
+
|
|
22
|
+
// Repository Factories
|
|
23
|
+
const {
|
|
24
|
+
createAdminProcessRepository,
|
|
25
|
+
AdminProcessRepositoryMongo,
|
|
26
|
+
AdminProcessRepositoryPostgres,
|
|
27
|
+
AdminProcessRepositoryDocumentDB,
|
|
28
|
+
} = require('./repositories/admin-process-repository-factory');
|
|
29
|
+
const {
|
|
30
|
+
createScriptScheduleRepository,
|
|
31
|
+
ScriptScheduleRepositoryMongo,
|
|
32
|
+
ScriptScheduleRepositoryPostgres,
|
|
33
|
+
ScriptScheduleRepositoryDocumentDB,
|
|
34
|
+
} = require('./repositories/script-schedule-repository-factory');
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
// Repository Interfaces
|
|
38
|
+
AdminProcessRepositoryInterface,
|
|
39
|
+
ScriptScheduleRepositoryInterface,
|
|
40
|
+
|
|
41
|
+
// Repository Factories (primary exports for use cases)
|
|
42
|
+
createAdminProcessRepository,
|
|
43
|
+
createScriptScheduleRepository,
|
|
44
|
+
|
|
45
|
+
// Concrete Implementations (for testing)
|
|
46
|
+
AdminProcessRepositoryMongo,
|
|
47
|
+
AdminProcessRepositoryPostgres,
|
|
48
|
+
AdminProcessRepositoryDocumentDB,
|
|
49
|
+
ScriptScheduleRepositoryMongo,
|
|
50
|
+
ScriptScheduleRepositoryPostgres,
|
|
51
|
+
ScriptScheduleRepositoryDocumentDB,
|
|
52
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const {
|
|
2
|
+
AdminApiKeyRepositoryMongo,
|
|
3
|
+
} = require('./admin-api-key-repository-mongo');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* DocumentDB Admin API Key Repository Adapter
|
|
7
|
+
* Extends MongoDB implementation since DocumentDB uses the same Prisma client
|
|
8
|
+
*
|
|
9
|
+
* DocumentDB-specific characteristics:
|
|
10
|
+
* - Uses MongoDB-compatible API
|
|
11
|
+
* - Prisma client handles the connection
|
|
12
|
+
* - IDs are strings with ObjectId format
|
|
13
|
+
* - All operations identical to MongoDB implementation
|
|
14
|
+
*/
|
|
15
|
+
class AdminApiKeyRepositoryDocumentDB extends AdminApiKeyRepositoryMongo {
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = { AdminApiKeyRepositoryDocumentDB };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const {
|
|
2
|
+
AdminApiKeyRepositoryPostgres,
|
|
3
|
+
} = require('./admin-api-key-repository-postgres');
|
|
4
|
+
const config = require('../../database/config');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Admin API Key Repository Factory
|
|
8
|
+
* Creates the appropriate repository adapter based on database type
|
|
9
|
+
*
|
|
10
|
+
* This implements the Factory pattern for Hexagonal Architecture:
|
|
11
|
+
* - Reads database type from app definition (backend/index.js)
|
|
12
|
+
* - Returns correct adapter (MongoDB, DocumentDB, or PostgreSQL)
|
|
13
|
+
* - Provides clear error for unsupported databases
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* ```javascript
|
|
17
|
+
* const repository = createAdminApiKeyRepository();
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @returns {AdminApiKeyRepositoryInterface} Configured repository adapter
|
|
21
|
+
* @throws {Error} If database type is not supported
|
|
22
|
+
*/
|
|
23
|
+
function createAdminApiKeyRepository() {
|
|
24
|
+
const dbType = config.DB_TYPE;
|
|
25
|
+
|
|
26
|
+
switch (dbType) {
|
|
27
|
+
case 'mongodb':
|
|
28
|
+
const { AdminApiKeyRepositoryMongo } = require('./admin-api-key-repository-mongo');
|
|
29
|
+
return new AdminApiKeyRepositoryMongo();
|
|
30
|
+
|
|
31
|
+
case 'postgresql':
|
|
32
|
+
return new AdminApiKeyRepositoryPostgres();
|
|
33
|
+
|
|
34
|
+
case 'documentdb':
|
|
35
|
+
const { AdminApiKeyRepositoryDocumentDB } = require('./admin-api-key-repository-documentdb');
|
|
36
|
+
return new AdminApiKeyRepositoryDocumentDB();
|
|
37
|
+
|
|
38
|
+
default:
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = {
|
|
46
|
+
createAdminApiKeyRepository,
|
|
47
|
+
// Export adapters for direct testing
|
|
48
|
+
get AdminApiKeyRepositoryMongo() { return require('./admin-api-key-repository-mongo').AdminApiKeyRepositoryMongo; },
|
|
49
|
+
AdminApiKeyRepositoryPostgres,
|
|
50
|
+
get AdminApiKeyRepositoryDocumentDB() { return require('./admin-api-key-repository-documentdb').AdminApiKeyRepositoryDocumentDB; },
|
|
51
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin API Key Repository Interface
|
|
3
|
+
* Abstract base class defining the contract for admin API key persistence adapters
|
|
4
|
+
*
|
|
5
|
+
* This follows the Port in Hexagonal Architecture:
|
|
6
|
+
* - Domain layer depends on this abstraction
|
|
7
|
+
* - Concrete adapters implement this interface
|
|
8
|
+
* - Use cases receive repositories via dependency injection
|
|
9
|
+
*
|
|
10
|
+
* Admin API keys provide authentication for script execution and management endpoints.
|
|
11
|
+
* Keys are bcrypt-hashed for security and support scoping and expiration.
|
|
12
|
+
*
|
|
13
|
+
* @abstract
|
|
14
|
+
*/
|
|
15
|
+
class AdminApiKeyRepositoryInterface {
|
|
16
|
+
/**
|
|
17
|
+
* Create a new admin API key
|
|
18
|
+
*
|
|
19
|
+
* @param {Object} params - API key creation parameters
|
|
20
|
+
* @param {string} params.name - Human-readable name for the key
|
|
21
|
+
* @param {string} params.keyHash - bcrypt hash of the raw key
|
|
22
|
+
* @param {string} params.keyLast4 - Last 4 characters of key (for display)
|
|
23
|
+
* @param {string[]} params.scopes - Array of permission scopes (e.g., ['scripts:execute', 'scripts:read'])
|
|
24
|
+
* @param {Date} [params.expiresAt] - Optional expiration date
|
|
25
|
+
* @param {string} [params.createdBy] - Optional identifier of creator (user/admin)
|
|
26
|
+
* @returns {Promise<Object>} The created API key record
|
|
27
|
+
* @abstract
|
|
28
|
+
*/
|
|
29
|
+
async createApiKey({
|
|
30
|
+
name,
|
|
31
|
+
keyHash,
|
|
32
|
+
keyLast4,
|
|
33
|
+
scopes,
|
|
34
|
+
expiresAt,
|
|
35
|
+
createdBy,
|
|
36
|
+
}) {
|
|
37
|
+
throw new Error('Method createApiKey must be implemented by subclass');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Find an API key by its bcrypt hash
|
|
42
|
+
* Used during authentication to validate incoming keys
|
|
43
|
+
*
|
|
44
|
+
* @param {string} keyHash - The bcrypt hash to search for
|
|
45
|
+
* @returns {Promise<Object|null>} The API key record or null if not found
|
|
46
|
+
* @abstract
|
|
47
|
+
*/
|
|
48
|
+
async findApiKeyByHash(keyHash) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
'Method findApiKeyByHash must be implemented by subclass'
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Find an API key by its ID
|
|
56
|
+
*
|
|
57
|
+
* @param {string|number} id - The API key ID
|
|
58
|
+
* @returns {Promise<Object|null>} The API key record or null if not found
|
|
59
|
+
* @abstract
|
|
60
|
+
*/
|
|
61
|
+
async findApiKeyById(id) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
'Method findApiKeyById must be implemented by subclass'
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Find all active (non-expired, non-deactivated) API keys
|
|
69
|
+
* Used during authentication to check all valid keys
|
|
70
|
+
*
|
|
71
|
+
* @returns {Promise<Array>} Array of active API key records
|
|
72
|
+
* @abstract
|
|
73
|
+
*/
|
|
74
|
+
async findActiveApiKeys() {
|
|
75
|
+
throw new Error(
|
|
76
|
+
'Method findActiveApiKeys must be implemented by subclass'
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Update the lastUsedAt timestamp for an API key
|
|
82
|
+
* Called after successful authentication
|
|
83
|
+
*
|
|
84
|
+
* @param {string|number} id - The API key ID
|
|
85
|
+
* @returns {Promise<Object>} Updated API key record
|
|
86
|
+
* @abstract
|
|
87
|
+
*/
|
|
88
|
+
async updateApiKeyLastUsed(id) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
'Method updateApiKeyLastUsed must be implemented by subclass'
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Deactivate an API key (soft delete)
|
|
96
|
+
* Sets isActive to false, preventing further use
|
|
97
|
+
*
|
|
98
|
+
* @param {string|number} id - The API key ID
|
|
99
|
+
* @returns {Promise<Object>} Updated API key record
|
|
100
|
+
* @abstract
|
|
101
|
+
*/
|
|
102
|
+
async deactivateApiKey(id) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
'Method deactivateApiKey must be implemented by subclass'
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Delete an API key (hard delete)
|
|
110
|
+
* Permanently removes the key from the database
|
|
111
|
+
*
|
|
112
|
+
* @param {string|number} id - The API key ID
|
|
113
|
+
* @returns {Promise<Object>} Deletion result
|
|
114
|
+
* @abstract
|
|
115
|
+
*/
|
|
116
|
+
async deleteApiKey(id) {
|
|
117
|
+
throw new Error('Method deleteApiKey must be implemented by subclass');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = { AdminApiKeyRepositoryInterface };
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const { prisma } = require('../../database/prisma');
|
|
2
|
+
const {
|
|
3
|
+
AdminApiKeyRepositoryInterface,
|
|
4
|
+
} = require('./admin-api-key-repository-interface');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* MongoDB Admin API Key Repository Adapter
|
|
8
|
+
* Handles admin API key persistence using Prisma with MongoDB
|
|
9
|
+
*
|
|
10
|
+
* MongoDB-specific characteristics:
|
|
11
|
+
* - IDs are strings with @db.ObjectId
|
|
12
|
+
* - Supports bcrypt hashed keys
|
|
13
|
+
* - Scopes stored as String[] array
|
|
14
|
+
*/
|
|
15
|
+
class AdminApiKeyRepositoryMongo extends AdminApiKeyRepositoryInterface {
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
this.prisma = prisma;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create a new admin API key
|
|
23
|
+
*
|
|
24
|
+
* @param {Object} params - API key creation parameters
|
|
25
|
+
* @param {string} params.name - Human-readable name for the key
|
|
26
|
+
* @param {string} params.keyHash - bcrypt hash of the raw key
|
|
27
|
+
* @param {string} params.keyLast4 - Last 4 characters of key (for display)
|
|
28
|
+
* @param {string[]} params.scopes - Array of permission scopes
|
|
29
|
+
* @param {Date} [params.expiresAt] - Optional expiration date
|
|
30
|
+
* @param {string} [params.createdBy] - Optional identifier of creator
|
|
31
|
+
* @returns {Promise<Object>} The created API key record
|
|
32
|
+
*/
|
|
33
|
+
async createApiKey({
|
|
34
|
+
name,
|
|
35
|
+
keyHash,
|
|
36
|
+
keyLast4,
|
|
37
|
+
scopes,
|
|
38
|
+
expiresAt,
|
|
39
|
+
createdBy,
|
|
40
|
+
}) {
|
|
41
|
+
const apiKey = await this.prisma.adminApiKey.create({
|
|
42
|
+
data: {
|
|
43
|
+
name,
|
|
44
|
+
keyHash,
|
|
45
|
+
keyLast4,
|
|
46
|
+
scopes,
|
|
47
|
+
expiresAt,
|
|
48
|
+
createdBy,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return apiKey;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Find an API key by its bcrypt hash
|
|
57
|
+
* Used during authentication to validate incoming keys
|
|
58
|
+
*
|
|
59
|
+
* @param {string} keyHash - The bcrypt hash to search for
|
|
60
|
+
* @returns {Promise<Object|null>} The API key record or null if not found
|
|
61
|
+
*/
|
|
62
|
+
async findApiKeyByHash(keyHash) {
|
|
63
|
+
const apiKey = await this.prisma.adminApiKey.findUnique({
|
|
64
|
+
where: { keyHash },
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return apiKey;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Find an API key by its ID
|
|
72
|
+
*
|
|
73
|
+
* @param {string} id - The API key ID (MongoDB ObjectId as string)
|
|
74
|
+
* @returns {Promise<Object|null>} The API key record or null if not found
|
|
75
|
+
*/
|
|
76
|
+
async findApiKeyById(id) {
|
|
77
|
+
const apiKey = await this.prisma.adminApiKey.findUnique({
|
|
78
|
+
where: { id },
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return apiKey;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Find all active (non-expired, non-deactivated) API keys
|
|
86
|
+
* Used during authentication to check all valid keys
|
|
87
|
+
*
|
|
88
|
+
* @returns {Promise<Array>} Array of active API key records
|
|
89
|
+
*/
|
|
90
|
+
async findActiveApiKeys() {
|
|
91
|
+
const now = new Date();
|
|
92
|
+
const apiKeys = await this.prisma.adminApiKey.findMany({
|
|
93
|
+
where: {
|
|
94
|
+
isActive: true,
|
|
95
|
+
OR: [{ expiresAt: null }, { expiresAt: { gt: now } }],
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return apiKeys;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Update the lastUsedAt timestamp for an API key
|
|
104
|
+
* Called after successful authentication
|
|
105
|
+
*
|
|
106
|
+
* @param {string} id - The API key ID
|
|
107
|
+
* @returns {Promise<Object>} Updated API key record
|
|
108
|
+
*/
|
|
109
|
+
async updateApiKeyLastUsed(id) {
|
|
110
|
+
const apiKey = await this.prisma.adminApiKey.update({
|
|
111
|
+
where: { id },
|
|
112
|
+
data: {
|
|
113
|
+
lastUsedAt: new Date(),
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return apiKey;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Deactivate an API key (soft delete)
|
|
122
|
+
* Sets isActive to false, preventing further use
|
|
123
|
+
*
|
|
124
|
+
* @param {string} id - The API key ID
|
|
125
|
+
* @returns {Promise<Object>} Updated API key record
|
|
126
|
+
*/
|
|
127
|
+
async deactivateApiKey(id) {
|
|
128
|
+
const apiKey = await this.prisma.adminApiKey.update({
|
|
129
|
+
where: { id },
|
|
130
|
+
data: {
|
|
131
|
+
isActive: false,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return apiKey;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Delete an API key (hard delete)
|
|
140
|
+
* Permanently removes the key from the database
|
|
141
|
+
*
|
|
142
|
+
* @param {string} id - The API key ID
|
|
143
|
+
* @returns {Promise<Object>} Deletion result
|
|
144
|
+
*/
|
|
145
|
+
async deleteApiKey(id) {
|
|
146
|
+
await this.prisma.adminApiKey.delete({
|
|
147
|
+
where: { id },
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Return Mongoose-compatible result
|
|
151
|
+
return { acknowledged: true, deletedCount: 1 };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = { AdminApiKeyRepositoryMongo };
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
const { prisma } = require('../../database/prisma');
|
|
2
|
+
const {
|
|
3
|
+
AdminApiKeyRepositoryInterface,
|
|
4
|
+
} = require('./admin-api-key-repository-interface');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* PostgreSQL Admin API Key Repository Adapter
|
|
8
|
+
* Handles admin API key persistence using Prisma with PostgreSQL
|
|
9
|
+
*
|
|
10
|
+
* PostgreSQL-specific characteristics:
|
|
11
|
+
* - Uses Int IDs with autoincrement
|
|
12
|
+
* - Requires ID conversion: String (app layer) ↔ Int (database)
|
|
13
|
+
* - All returned IDs are converted to strings for application layer consistency
|
|
14
|
+
*/
|
|
15
|
+
class AdminApiKeyRepositoryPostgres extends AdminApiKeyRepositoryInterface {
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
this.prisma = prisma;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Convert string ID to integer for PostgreSQL queries
|
|
23
|
+
* @private
|
|
24
|
+
* @param {string|number|null|undefined} id - ID to convert
|
|
25
|
+
* @returns {number|null|undefined} Integer ID or null/undefined
|
|
26
|
+
* @throws {Error} If ID cannot be converted to integer
|
|
27
|
+
*/
|
|
28
|
+
_convertId(id) {
|
|
29
|
+
if (id === null || id === undefined) return id;
|
|
30
|
+
const parsed = Number.parseInt(id, 10);
|
|
31
|
+
if (Number.isNaN(parsed)) {
|
|
32
|
+
throw new Error(`Invalid ID: ${id} cannot be converted to integer`);
|
|
33
|
+
}
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Convert API key object IDs to strings
|
|
39
|
+
* @private
|
|
40
|
+
* @param {Object|null} apiKey - API key object from database
|
|
41
|
+
* @returns {Object|null} API key with string IDs
|
|
42
|
+
*/
|
|
43
|
+
_convertApiKeyIds(apiKey) {
|
|
44
|
+
if (!apiKey) return apiKey;
|
|
45
|
+
return {
|
|
46
|
+
...apiKey,
|
|
47
|
+
id: apiKey.id?.toString(),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Create a new admin API key
|
|
53
|
+
*
|
|
54
|
+
* @param {Object} params - API key creation parameters
|
|
55
|
+
* @param {string} params.name - Human-readable name for the key
|
|
56
|
+
* @param {string} params.keyHash - bcrypt hash of the raw key
|
|
57
|
+
* @param {string} params.keyLast4 - Last 4 characters of key (for display)
|
|
58
|
+
* @param {string[]} params.scopes - Array of permission scopes
|
|
59
|
+
* @param {Date} [params.expiresAt] - Optional expiration date
|
|
60
|
+
* @param {string} [params.createdBy] - Optional identifier of creator
|
|
61
|
+
* @returns {Promise<Object>} The created API key record with string ID
|
|
62
|
+
*/
|
|
63
|
+
async createApiKey({
|
|
64
|
+
name,
|
|
65
|
+
keyHash,
|
|
66
|
+
keyLast4,
|
|
67
|
+
scopes,
|
|
68
|
+
expiresAt,
|
|
69
|
+
createdBy,
|
|
70
|
+
}) {
|
|
71
|
+
const apiKey = await this.prisma.adminApiKey.create({
|
|
72
|
+
data: {
|
|
73
|
+
name,
|
|
74
|
+
keyHash,
|
|
75
|
+
keyLast4,
|
|
76
|
+
scopes,
|
|
77
|
+
expiresAt,
|
|
78
|
+
createdBy,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return this._convertApiKeyIds(apiKey);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Find an API key by its bcrypt hash
|
|
87
|
+
* Used during authentication to validate incoming keys
|
|
88
|
+
*
|
|
89
|
+
* @param {string} keyHash - The bcrypt hash to search for
|
|
90
|
+
* @returns {Promise<Object|null>} The API key record with string ID or null if not found
|
|
91
|
+
*/
|
|
92
|
+
async findApiKeyByHash(keyHash) {
|
|
93
|
+
const apiKey = await this.prisma.adminApiKey.findUnique({
|
|
94
|
+
where: { keyHash },
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return this._convertApiKeyIds(apiKey);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Find an API key by its ID
|
|
102
|
+
*
|
|
103
|
+
* @param {string|number} id - The API key ID
|
|
104
|
+
* @returns {Promise<Object|null>} The API key record with string ID or null if not found
|
|
105
|
+
*/
|
|
106
|
+
async findApiKeyById(id) {
|
|
107
|
+
const intId = this._convertId(id);
|
|
108
|
+
const apiKey = await this.prisma.adminApiKey.findUnique({
|
|
109
|
+
where: { id: intId },
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return this._convertApiKeyIds(apiKey);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Find all active (non-expired, non-deactivated) API keys
|
|
117
|
+
* Used during authentication to check all valid keys
|
|
118
|
+
*
|
|
119
|
+
* @returns {Promise<Array>} Array of active API key records with string IDs
|
|
120
|
+
*/
|
|
121
|
+
async findActiveApiKeys() {
|
|
122
|
+
const now = new Date();
|
|
123
|
+
const apiKeys = await this.prisma.adminApiKey.findMany({
|
|
124
|
+
where: {
|
|
125
|
+
isActive: true,
|
|
126
|
+
OR: [{ expiresAt: null }, { expiresAt: { gt: now } }],
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return apiKeys.map((apiKey) => this._convertApiKeyIds(apiKey));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Update the lastUsedAt timestamp for an API key
|
|
135
|
+
* Called after successful authentication
|
|
136
|
+
*
|
|
137
|
+
* @param {string|number} id - The API key ID
|
|
138
|
+
* @returns {Promise<Object>} Updated API key record with string ID
|
|
139
|
+
*/
|
|
140
|
+
async updateApiKeyLastUsed(id) {
|
|
141
|
+
const intId = this._convertId(id);
|
|
142
|
+
const apiKey = await this.prisma.adminApiKey.update({
|
|
143
|
+
where: { id: intId },
|
|
144
|
+
data: {
|
|
145
|
+
lastUsedAt: new Date(),
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return this._convertApiKeyIds(apiKey);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Deactivate an API key (soft delete)
|
|
154
|
+
* Sets isActive to false, preventing further use
|
|
155
|
+
*
|
|
156
|
+
* @param {string|number} id - The API key ID
|
|
157
|
+
* @returns {Promise<Object>} Updated API key record with string ID
|
|
158
|
+
*/
|
|
159
|
+
async deactivateApiKey(id) {
|
|
160
|
+
const intId = this._convertId(id);
|
|
161
|
+
const apiKey = await this.prisma.adminApiKey.update({
|
|
162
|
+
where: { id: intId },
|
|
163
|
+
data: {
|
|
164
|
+
isActive: false,
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return this._convertApiKeyIds(apiKey);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Delete an API key (hard delete)
|
|
173
|
+
* Permanently removes the key from the database
|
|
174
|
+
*
|
|
175
|
+
* @param {string|number} id - The API key ID
|
|
176
|
+
* @returns {Promise<Object>} Deletion result
|
|
177
|
+
*/
|
|
178
|
+
async deleteApiKey(id) {
|
|
179
|
+
const intId = this._convertId(id);
|
|
180
|
+
await this.prisma.adminApiKey.delete({
|
|
181
|
+
where: { id: intId },
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Return Mongoose-compatible result
|
|
185
|
+
return { acknowledged: true, deletedCount: 1 };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = { AdminApiKeyRepositoryPostgres };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const {
|
|
2
|
+
AdminProcessRepositoryMongo,
|
|
3
|
+
} = require('./admin-process-repository-mongo');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* DocumentDB Admin Process Repository Adapter
|
|
7
|
+
* Extends MongoDB implementation since DocumentDB uses the same Prisma client
|
|
8
|
+
*
|
|
9
|
+
* DocumentDB-specific characteristics:
|
|
10
|
+
* - Uses MongoDB-compatible API
|
|
11
|
+
* - Prisma client handles the connection
|
|
12
|
+
* - IDs are strings with ObjectId format
|
|
13
|
+
* - All operations identical to MongoDB implementation
|
|
14
|
+
*/
|
|
15
|
+
class AdminProcessRepositoryDocumentDB extends AdminProcessRepositoryMongo {
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = { AdminProcessRepositoryDocumentDB };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const { AdminProcessRepositoryPostgres } = require('./admin-process-repository-postgres');
|
|
2
|
+
const config = require('../../database/config');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Admin Process Repository Factory
|
|
6
|
+
* Creates the appropriate repository adapter based on database type
|
|
7
|
+
*
|
|
8
|
+
* This implements the Factory pattern for Hexagonal Architecture:
|
|
9
|
+
* - Reads database type from app definition (backend/index.js)
|
|
10
|
+
* - Returns correct adapter (MongoDB, DocumentDB, or PostgreSQL)
|
|
11
|
+
* - Provides clear error for unsupported databases
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* ```javascript
|
|
15
|
+
* const repository = createAdminProcessRepository();
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @returns {AdminProcessRepositoryInterface} Configured repository adapter
|
|
19
|
+
* @throws {Error} If database type is not supported
|
|
20
|
+
*/
|
|
21
|
+
function createAdminProcessRepository() {
|
|
22
|
+
const dbType = config.DB_TYPE;
|
|
23
|
+
|
|
24
|
+
switch (dbType) {
|
|
25
|
+
case 'mongodb':
|
|
26
|
+
const { AdminProcessRepositoryMongo } = require('./admin-process-repository-mongo');
|
|
27
|
+
return new AdminProcessRepositoryMongo();
|
|
28
|
+
|
|
29
|
+
case 'postgresql':
|
|
30
|
+
return new AdminProcessRepositoryPostgres();
|
|
31
|
+
|
|
32
|
+
case 'documentdb':
|
|
33
|
+
const { AdminProcessRepositoryDocumentDB } = require('./admin-process-repository-documentdb');
|
|
34
|
+
return new AdminProcessRepositoryDocumentDB();
|
|
35
|
+
|
|
36
|
+
default:
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
createAdminProcessRepository,
|
|
45
|
+
// Export adapters for direct testing
|
|
46
|
+
get AdminProcessRepositoryMongo() { return require('./admin-process-repository-mongo').AdminProcessRepositoryMongo; },
|
|
47
|
+
AdminProcessRepositoryPostgres,
|
|
48
|
+
get AdminProcessRepositoryDocumentDB() { return require('./admin-process-repository-documentdb').AdminProcessRepositoryDocumentDB; },
|
|
49
|
+
};
|