@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
|
@@ -1,450 +0,0 @@
|
|
|
1
|
-
const express = require('express');
|
|
2
|
-
const router = express.Router();
|
|
3
|
-
const { createAppHandler } = require('./../app-handler-helpers');
|
|
4
|
-
const { requireAdmin } = require('./middleware/requireAdmin');
|
|
5
|
-
const catchAsyncError = require('express-async-handler');
|
|
6
|
-
const bcrypt = require('bcryptjs');
|
|
7
|
-
const {
|
|
8
|
-
createUserRepository,
|
|
9
|
-
} = require('../../user/repositories/user-repository-factory');
|
|
10
|
-
const { loadAppDefinition } = require('../app-definition-loader');
|
|
11
|
-
const {
|
|
12
|
-
createModuleRepository,
|
|
13
|
-
} = require('../../modules/repositories/module-repository-factory');
|
|
14
|
-
const {
|
|
15
|
-
GetModuleEntityById,
|
|
16
|
-
} = require('../../modules/use-cases/get-module-entity-by-id');
|
|
17
|
-
const {
|
|
18
|
-
UpdateModuleEntity,
|
|
19
|
-
} = require('../../modules/use-cases/update-module-entity');
|
|
20
|
-
const {
|
|
21
|
-
DeleteModuleEntity,
|
|
22
|
-
} = require('../../modules/use-cases/delete-module-entity');
|
|
23
|
-
const {
|
|
24
|
-
CreateTokenForUserId,
|
|
25
|
-
} = require('../../user/use-cases/create-token-for-user-id');
|
|
26
|
-
const { DeleteUser } = require('../../user/use-cases/delete-user');
|
|
27
|
-
|
|
28
|
-
// Initialize repositories and use cases
|
|
29
|
-
const { userConfig } = loadAppDefinition();
|
|
30
|
-
const userRepository = createUserRepository({ userConfig });
|
|
31
|
-
const moduleRepository = createModuleRepository();
|
|
32
|
-
|
|
33
|
-
// Use cases
|
|
34
|
-
const getModuleEntityById = new GetModuleEntityById({ moduleRepository });
|
|
35
|
-
const updateModuleEntity = new UpdateModuleEntity({ moduleRepository });
|
|
36
|
-
const deleteModuleEntity = new DeleteModuleEntity({ moduleRepository });
|
|
37
|
-
const createTokenForUserId = new CreateTokenForUserId({ userRepository });
|
|
38
|
-
const deleteUser = new DeleteUser({ userRepository });
|
|
39
|
-
|
|
40
|
-
// Debug logging
|
|
41
|
-
router.use((req, res, next) => {
|
|
42
|
-
console.log(
|
|
43
|
-
`[Admin Router] ${req.method} ${req.path} | Original URL: ${req.originalUrl}`
|
|
44
|
-
);
|
|
45
|
-
next();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
// Apply admin API key auth middleware to all admin routes
|
|
49
|
-
router.use(requireAdmin);
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* USER MANAGEMENT ENDPOINTS
|
|
53
|
-
*/
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* GET /api/admin/users
|
|
57
|
-
* List all users with pagination
|
|
58
|
-
*/
|
|
59
|
-
router.get(
|
|
60
|
-
'/api/admin/users',
|
|
61
|
-
catchAsyncError(async (req, res) => {
|
|
62
|
-
const {
|
|
63
|
-
page = 1,
|
|
64
|
-
limit = 50,
|
|
65
|
-
sortBy = 'createdAt',
|
|
66
|
-
sortOrder = 'desc',
|
|
67
|
-
} = req.query;
|
|
68
|
-
const skip = (parseInt(page) - 1) * parseInt(limit);
|
|
69
|
-
|
|
70
|
-
// Build sort object
|
|
71
|
-
const sort = {};
|
|
72
|
-
sort[sortBy] = sortOrder === 'desc' ? -1 : 1;
|
|
73
|
-
|
|
74
|
-
// Use repository to get users
|
|
75
|
-
const users = await userRepository.findAllUsers({
|
|
76
|
-
skip,
|
|
77
|
-
limit: parseInt(limit),
|
|
78
|
-
sort,
|
|
79
|
-
excludeFields: ['-hashword'], // Exclude password hash
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const totalCount = await userRepository.countUsers();
|
|
83
|
-
|
|
84
|
-
res.json({
|
|
85
|
-
users,
|
|
86
|
-
pagination: {
|
|
87
|
-
page: parseInt(page),
|
|
88
|
-
limit: parseInt(limit),
|
|
89
|
-
total: totalCount,
|
|
90
|
-
pages: Math.ceil(totalCount / parseInt(limit)),
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
})
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* GET /api/admin/users/search
|
|
98
|
-
* Search users by username or email
|
|
99
|
-
*/
|
|
100
|
-
router.get(
|
|
101
|
-
'/api/admin/users/search',
|
|
102
|
-
catchAsyncError(async (req, res) => {
|
|
103
|
-
const {
|
|
104
|
-
q,
|
|
105
|
-
page = 1,
|
|
106
|
-
limit = 50,
|
|
107
|
-
sortBy = 'createdAt',
|
|
108
|
-
sortOrder = 'desc',
|
|
109
|
-
} = req.query;
|
|
110
|
-
|
|
111
|
-
if (!q) {
|
|
112
|
-
return res.status(400).json({
|
|
113
|
-
status: 'error',
|
|
114
|
-
message: 'Search query parameter "q" is required',
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const skip = (parseInt(page) - 1) * parseInt(limit);
|
|
119
|
-
|
|
120
|
-
// Build sort object
|
|
121
|
-
const sort = {};
|
|
122
|
-
sort[sortBy] = sortOrder === 'desc' ? -1 : 1;
|
|
123
|
-
|
|
124
|
-
// Use repository to search users
|
|
125
|
-
const users = await userRepository.searchUsers({
|
|
126
|
-
query: q,
|
|
127
|
-
skip,
|
|
128
|
-
limit: parseInt(limit),
|
|
129
|
-
sort,
|
|
130
|
-
excludeFields: ['-hashword'],
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
const totalCount = await userRepository.countUsersBySearchQuery(q);
|
|
134
|
-
|
|
135
|
-
res.json({
|
|
136
|
-
users,
|
|
137
|
-
pagination: {
|
|
138
|
-
page: parseInt(page),
|
|
139
|
-
limit: parseInt(limit),
|
|
140
|
-
total: totalCount,
|
|
141
|
-
pages: Math.ceil(totalCount / parseInt(limit)),
|
|
142
|
-
},
|
|
143
|
-
});
|
|
144
|
-
})
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* POST /api/admin/users
|
|
149
|
-
* Create a new user (admin only)
|
|
150
|
-
* Admin-specific features:
|
|
151
|
-
* - Can create users with custom roles
|
|
152
|
-
* - Can set verified status
|
|
153
|
-
* - Can assign to organizations
|
|
154
|
-
* - No email verification required
|
|
155
|
-
*/
|
|
156
|
-
router.post(
|
|
157
|
-
'/api/admin/users',
|
|
158
|
-
catchAsyncError(async (req, res) => {
|
|
159
|
-
const {
|
|
160
|
-
username,
|
|
161
|
-
email,
|
|
162
|
-
password,
|
|
163
|
-
type = 'INDIVIDUAL',
|
|
164
|
-
appUserId,
|
|
165
|
-
organizationId,
|
|
166
|
-
verified = true, // Admins can create pre-verified users
|
|
167
|
-
} = req.body;
|
|
168
|
-
|
|
169
|
-
// Validate required fields
|
|
170
|
-
if (!username || !email || !password) {
|
|
171
|
-
return res.status(400).json({
|
|
172
|
-
status: 'error',
|
|
173
|
-
message: 'Username, email, and password are required',
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Check if user already exists
|
|
178
|
-
const existingUser = await userRepository.findIndividualUserByUsername(
|
|
179
|
-
username
|
|
180
|
-
);
|
|
181
|
-
if (existingUser) {
|
|
182
|
-
return res.status(409).json({
|
|
183
|
-
status: 'error',
|
|
184
|
-
message: 'User with this username already exists',
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const existingEmail = await userRepository.findIndividualUserByEmail(
|
|
189
|
-
email
|
|
190
|
-
);
|
|
191
|
-
if (existingEmail) {
|
|
192
|
-
return res.status(409).json({
|
|
193
|
-
status: 'error',
|
|
194
|
-
message: 'User with this email already exists',
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Hash password (using bcryptjs which is already imported)
|
|
199
|
-
const hashword = await bcrypt.hash(password, 10);
|
|
200
|
-
|
|
201
|
-
// Create user with admin-specified attributes
|
|
202
|
-
const userData = {
|
|
203
|
-
username,
|
|
204
|
-
email,
|
|
205
|
-
hashword,
|
|
206
|
-
type,
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
// Add optional fields if provided
|
|
210
|
-
if (appUserId) userData.appUserId = appUserId;
|
|
211
|
-
if (organizationId) userData.organizationId = organizationId;
|
|
212
|
-
|
|
213
|
-
const user = await userRepository.createIndividualUser(userData);
|
|
214
|
-
|
|
215
|
-
// Remove sensitive fields
|
|
216
|
-
const userObj = user.toObject ? user.toObject() : user;
|
|
217
|
-
delete userObj.hashword;
|
|
218
|
-
|
|
219
|
-
res.status(201).json({
|
|
220
|
-
user: userObj,
|
|
221
|
-
message: 'User created successfully by admin',
|
|
222
|
-
});
|
|
223
|
-
})
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* GET /api/admin/users/:userId
|
|
228
|
-
* Get a specific user by ID
|
|
229
|
-
*/
|
|
230
|
-
router.get(
|
|
231
|
-
'/api/admin/users/:userId',
|
|
232
|
-
catchAsyncError(async (req, res) => {
|
|
233
|
-
const { userId } = req.params;
|
|
234
|
-
|
|
235
|
-
const user = await userRepository.findUserById(userId);
|
|
236
|
-
|
|
237
|
-
if (!user) {
|
|
238
|
-
return res.status(404).json({
|
|
239
|
-
status: 'error',
|
|
240
|
-
message: 'User not found',
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Remove sensitive fields
|
|
245
|
-
const userObj = user.toObject ? user.toObject() : user;
|
|
246
|
-
delete userObj.hashword;
|
|
247
|
-
|
|
248
|
-
res.json({ user: userObj });
|
|
249
|
-
})
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* POST /api/admin/users/:userId/impersonate
|
|
254
|
-
* Generate a token for a user without requiring password (admin impersonation)
|
|
255
|
-
* Allows admins to login as any user for support/testing purposes
|
|
256
|
-
*/
|
|
257
|
-
router.post(
|
|
258
|
-
'/api/admin/users/:userId/impersonate',
|
|
259
|
-
catchAsyncError(async (req, res) => {
|
|
260
|
-
const { userId } = req.params;
|
|
261
|
-
const { expiresInMinutes = 120 } = req.body;
|
|
262
|
-
|
|
263
|
-
// Find the user
|
|
264
|
-
const user = await userRepository.findUserById(userId);
|
|
265
|
-
|
|
266
|
-
if (!user) {
|
|
267
|
-
return res.status(404).json({
|
|
268
|
-
status: 'error',
|
|
269
|
-
message: 'User not found',
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Generate token without password verification
|
|
274
|
-
const token = await createTokenForUserId.execute(
|
|
275
|
-
userId,
|
|
276
|
-
expiresInMinutes
|
|
277
|
-
);
|
|
278
|
-
|
|
279
|
-
res.json({
|
|
280
|
-
token,
|
|
281
|
-
message: `Impersonating user: ${user.username || user.email}`,
|
|
282
|
-
expiresInMinutes,
|
|
283
|
-
});
|
|
284
|
-
})
|
|
285
|
-
);
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* DELETE /api/admin/users/:userId
|
|
289
|
-
* Delete a user by ID (admin only)
|
|
290
|
-
* IMPORTANT: This is a destructive operation - use with caution
|
|
291
|
-
*/
|
|
292
|
-
router.delete(
|
|
293
|
-
'/api/admin/users/:userId',
|
|
294
|
-
catchAsyncError(async (req, res) => {
|
|
295
|
-
const { userId } = req.params;
|
|
296
|
-
|
|
297
|
-
// Execute delete user use case
|
|
298
|
-
await deleteUser.execute(userId);
|
|
299
|
-
|
|
300
|
-
res.status(204).send();
|
|
301
|
-
})
|
|
302
|
-
);
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* GLOBAL ENTITY MANAGEMENT ENDPOINTS
|
|
306
|
-
*/
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* GET /api/admin/entities
|
|
310
|
-
* List all global entities
|
|
311
|
-
*/
|
|
312
|
-
router.get(
|
|
313
|
-
'/api/admin/entities',
|
|
314
|
-
catchAsyncError(async (req, res) => {
|
|
315
|
-
const { type, status } = req.query;
|
|
316
|
-
|
|
317
|
-
const query = { isGlobal: true };
|
|
318
|
-
if (type) query.type = type;
|
|
319
|
-
if (status) query.status = status;
|
|
320
|
-
|
|
321
|
-
const entities = await moduleRepository.findEntitiesBy(query);
|
|
322
|
-
|
|
323
|
-
res.json({ entities });
|
|
324
|
-
})
|
|
325
|
-
);
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* GET /api/admin/entities/:entityId
|
|
329
|
-
* Get a specific global entity
|
|
330
|
-
*/
|
|
331
|
-
router.get(
|
|
332
|
-
'/api/admin/entities/:entityId',
|
|
333
|
-
catchAsyncError(async (req, res) => {
|
|
334
|
-
const { entityId } = req.params;
|
|
335
|
-
|
|
336
|
-
const entity = await getModuleEntityById.execute(entityId);
|
|
337
|
-
|
|
338
|
-
if (!entity || !entity.isGlobal) {
|
|
339
|
-
return res.status(404).json({
|
|
340
|
-
status: 'error',
|
|
341
|
-
message: 'Global entity not found',
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
res.json({ entity });
|
|
346
|
-
})
|
|
347
|
-
);
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* POST /api/admin/entities
|
|
351
|
-
* Create a new global entity
|
|
352
|
-
*/
|
|
353
|
-
router.post(
|
|
354
|
-
'/api/admin/entities',
|
|
355
|
-
catchAsyncError(async (req, res) => {
|
|
356
|
-
const { type, ...entityData } = req.body;
|
|
357
|
-
|
|
358
|
-
if (!type) {
|
|
359
|
-
return res.status(400).json({
|
|
360
|
-
status: 'error',
|
|
361
|
-
message: 'Entity type is required',
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Create entity with isGlobal flag
|
|
366
|
-
const entity = await moduleRepository.createEntity({
|
|
367
|
-
...entityData,
|
|
368
|
-
type,
|
|
369
|
-
isGlobal: true,
|
|
370
|
-
status: 'connected',
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
res.status(201).json({ entity });
|
|
374
|
-
})
|
|
375
|
-
);
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* PUT /api/admin/entities/:entityId
|
|
379
|
-
* Update a global entity
|
|
380
|
-
*/
|
|
381
|
-
router.put(
|
|
382
|
-
'/api/admin/entities/:entityId',
|
|
383
|
-
catchAsyncError(async (req, res) => {
|
|
384
|
-
const { entityId } = req.params;
|
|
385
|
-
|
|
386
|
-
const entity = await updateModuleEntity.execute(entityId, req.body);
|
|
387
|
-
|
|
388
|
-
if (!entity) {
|
|
389
|
-
return res.status(404).json({
|
|
390
|
-
status: 'error',
|
|
391
|
-
message: 'Global entity not found',
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
res.json({ entity });
|
|
396
|
-
})
|
|
397
|
-
);
|
|
398
|
-
|
|
399
|
-
/**
|
|
400
|
-
* DELETE /api/admin/entities/:entityId
|
|
401
|
-
* Delete a global entity
|
|
402
|
-
*/
|
|
403
|
-
router.delete(
|
|
404
|
-
'/api/admin/entities/:entityId',
|
|
405
|
-
catchAsyncError(async (req, res) => {
|
|
406
|
-
const { entityId } = req.params;
|
|
407
|
-
|
|
408
|
-
await deleteModuleEntity.execute(entityId);
|
|
409
|
-
|
|
410
|
-
res.status(204).send();
|
|
411
|
-
})
|
|
412
|
-
);
|
|
413
|
-
|
|
414
|
-
/**
|
|
415
|
-
* POST /api/admin/entities/:entityId/test
|
|
416
|
-
* Test connection for a global entity
|
|
417
|
-
*/
|
|
418
|
-
router.post(
|
|
419
|
-
'/api/admin/entities/:entityId/test',
|
|
420
|
-
catchAsyncError(async (req, res) => {
|
|
421
|
-
const { entityId } = req.params;
|
|
422
|
-
|
|
423
|
-
const entity = await getModuleEntityById.execute(entityId);
|
|
424
|
-
|
|
425
|
-
if (!entity || !entity.isGlobal) {
|
|
426
|
-
return res.status(404).json({
|
|
427
|
-
status: 'error',
|
|
428
|
-
message: 'Global entity not found',
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// Test the entity connection
|
|
433
|
-
try {
|
|
434
|
-
// This would use a TestModuleAuth use case
|
|
435
|
-
res.json({
|
|
436
|
-
status: 'success',
|
|
437
|
-
message: 'Entity connection test successful',
|
|
438
|
-
});
|
|
439
|
-
} catch (error) {
|
|
440
|
-
res.status(500).json({
|
|
441
|
-
status: 'error',
|
|
442
|
-
message: `Entity connection test failed: ${error.message}`,
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
})
|
|
446
|
-
);
|
|
447
|
-
|
|
448
|
-
const handler = createAppHandler('HTTP Event: Admin', router);
|
|
449
|
-
|
|
450
|
-
module.exports = { handler, router };
|
package/handlers/routers/docs.js
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* API Documentation Router
|
|
3
|
-
*
|
|
4
|
-
* Serves dynamic OpenAPI specs and Scalar UI documentation for both v1 and v2 APIs.
|
|
5
|
-
* Specs are generated dynamically based on appDefinition and installed modules.
|
|
6
|
-
*
|
|
7
|
-
* Endpoints:
|
|
8
|
-
* - GET /api/docs - Main documentation UI with version selector
|
|
9
|
-
* - GET /api/v1/docs - v1-specific documentation
|
|
10
|
-
* - GET /api/v2/docs - v2-specific documentation
|
|
11
|
-
* - GET /api/openapi.json - v2 spec (default/current)
|
|
12
|
-
* - GET /api/openapi-v1.json - v1 spec
|
|
13
|
-
* - GET /api/openapi-v2.json - v2 spec
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const { Router } = require('express');
|
|
17
|
-
const { createAppHandler } = require('./../app-handler-helpers');
|
|
18
|
-
const {
|
|
19
|
-
generateOpenApiSpec,
|
|
20
|
-
generateOpenApiSpecV1,
|
|
21
|
-
generateOpenApiSpecV2,
|
|
22
|
-
} = require('../../openapi/openapi-spec-generator');
|
|
23
|
-
|
|
24
|
-
const router = Router();
|
|
25
|
-
|
|
26
|
-
let cachedAppDefinition = null;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Load the appDefinition for spec generation
|
|
30
|
-
* Lazy-loads and caches to avoid performance overhead
|
|
31
|
-
*/
|
|
32
|
-
function loadAppDefinitionForDocs() {
|
|
33
|
-
if (cachedAppDefinition) return cachedAppDefinition;
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
const { loadAppDefinition } = require('../app-definition-loader');
|
|
37
|
-
const { integrations } = loadAppDefinition();
|
|
38
|
-
cachedAppDefinition = { integrations };
|
|
39
|
-
return cachedAppDefinition;
|
|
40
|
-
} catch (error) {
|
|
41
|
-
// App definition not available (e.g., in test environment)
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Generate Scalar HTML with version selector
|
|
48
|
-
* @param {Object} options - Configuration options
|
|
49
|
-
* @param {string} options.specUrl - Primary spec URL
|
|
50
|
-
* @param {Array} options.sources - Array of spec sources for version selector
|
|
51
|
-
* @param {string} options.title - Page title
|
|
52
|
-
*/
|
|
53
|
-
function generateScalarHtml({
|
|
54
|
-
specUrl,
|
|
55
|
-
sources,
|
|
56
|
-
title = 'Frigg API Documentation',
|
|
57
|
-
}) {
|
|
58
|
-
// If sources provided, use multi-spec configuration
|
|
59
|
-
const config = sources ? { sources } : { url: specUrl };
|
|
60
|
-
|
|
61
|
-
return `<!DOCTYPE html>
|
|
62
|
-
<html>
|
|
63
|
-
<head>
|
|
64
|
-
<title>${title}</title>
|
|
65
|
-
<meta charset="utf-8" />
|
|
66
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
67
|
-
<style>
|
|
68
|
-
body { margin: 0; padding: 0; }
|
|
69
|
-
</style>
|
|
70
|
-
</head>
|
|
71
|
-
<body>
|
|
72
|
-
<script id="api-reference" data-configuration='${JSON.stringify(
|
|
73
|
-
config
|
|
74
|
-
)}'></script>
|
|
75
|
-
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
|
|
76
|
-
</body>
|
|
77
|
-
</html>`;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ============================================================================
|
|
81
|
-
// OpenAPI Spec Endpoints
|
|
82
|
-
// ============================================================================
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* GET /api/openapi.json - Default (v2) OpenAPI spec
|
|
86
|
-
*/
|
|
87
|
-
router.get('/api/openapi.json', (req, res) => {
|
|
88
|
-
try {
|
|
89
|
-
const serverUrl = `${req.protocol}://${req.get('host')}`;
|
|
90
|
-
const appDefinition = loadAppDefinitionForDocs();
|
|
91
|
-
const spec = generateOpenApiSpecV2(appDefinition, { serverUrl });
|
|
92
|
-
res.json(spec);
|
|
93
|
-
} catch (error) {
|
|
94
|
-
console.error('Failed to generate OpenAPI spec:', error.message);
|
|
95
|
-
res.status(500).json({ error: 'Failed to load API specification' });
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* GET /api/openapi-v1.json - v1 API OpenAPI spec
|
|
101
|
-
*/
|
|
102
|
-
router.get('/api/openapi-v1.json', (req, res) => {
|
|
103
|
-
try {
|
|
104
|
-
const serverUrl = `${req.protocol}://${req.get('host')}`;
|
|
105
|
-
const appDefinition = loadAppDefinitionForDocs();
|
|
106
|
-
const spec = generateOpenApiSpecV1(appDefinition, { serverUrl });
|
|
107
|
-
res.json(spec);
|
|
108
|
-
} catch (error) {
|
|
109
|
-
console.error('Failed to generate v1 OpenAPI spec:', error.message);
|
|
110
|
-
res.status(500).json({ error: 'Failed to load API specification' });
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* GET /api/openapi-v2.json - v2 API OpenAPI spec
|
|
116
|
-
*/
|
|
117
|
-
router.get('/api/openapi-v2.json', (req, res) => {
|
|
118
|
-
try {
|
|
119
|
-
const serverUrl = `${req.protocol}://${req.get('host')}`;
|
|
120
|
-
const appDefinition = loadAppDefinitionForDocs();
|
|
121
|
-
const spec = generateOpenApiSpecV2(appDefinition, { serverUrl });
|
|
122
|
-
res.json(spec);
|
|
123
|
-
} catch (error) {
|
|
124
|
-
console.error('Failed to generate v2 OpenAPI spec:', error.message);
|
|
125
|
-
res.status(500).json({ error: 'Failed to load API specification' });
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// ============================================================================
|
|
130
|
-
// Documentation UI Endpoints
|
|
131
|
-
// ============================================================================
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* GET /api/docs - Main documentation with version selector
|
|
135
|
-
* Shows both v1 and v2 APIs with a dropdown to switch between them
|
|
136
|
-
*/
|
|
137
|
-
router.get('/api/docs', (_req, res) => {
|
|
138
|
-
const html = generateScalarHtml({
|
|
139
|
-
sources: [
|
|
140
|
-
{
|
|
141
|
-
title: 'API v2 (Current)',
|
|
142
|
-
slug: 'v2',
|
|
143
|
-
url: '/api/openapi-v2.json',
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
title: 'API v1 (Legacy)',
|
|
147
|
-
slug: 'v1',
|
|
148
|
-
url: '/api/openapi-v1.json',
|
|
149
|
-
},
|
|
150
|
-
],
|
|
151
|
-
title: 'Frigg API Documentation',
|
|
152
|
-
});
|
|
153
|
-
res.type('html').send(html);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* GET /api/v1/docs - v1-specific documentation
|
|
158
|
-
*/
|
|
159
|
-
router.get('/api/v1/docs', (_req, res) => {
|
|
160
|
-
const html = generateScalarHtml({
|
|
161
|
-
specUrl: '/api/openapi-v1.json',
|
|
162
|
-
title: 'Frigg API v1 Documentation',
|
|
163
|
-
});
|
|
164
|
-
res.type('html').send(html);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* GET /api/v2/docs - v2-specific documentation
|
|
169
|
-
*/
|
|
170
|
-
router.get('/api/v2/docs', (_req, res) => {
|
|
171
|
-
const html = generateScalarHtml({
|
|
172
|
-
specUrl: '/api/openapi-v2.json',
|
|
173
|
-
title: 'Frigg API v2 Documentation',
|
|
174
|
-
});
|
|
175
|
-
res.type('html').send(html);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
const handler = createAppHandler('HTTP Event: Docs', router, false);
|
|
179
|
-
|
|
180
|
-
module.exports = { handler, router };
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
const catchAsyncError = require('express-async-handler');
|
|
2
|
-
const {
|
|
3
|
-
GetUserFromBearerToken,
|
|
4
|
-
} = require('../../../user/use-cases/get-user-from-bearer-token');
|
|
5
|
-
const {
|
|
6
|
-
createUserRepository,
|
|
7
|
-
} = require('../../../user/repositories/user-repository-factory');
|
|
8
|
-
const { loadAppDefinition } = require('../../app-definition-loader');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Load user from bearer token middleware
|
|
12
|
-
* Uses DDD pattern: Handler → Use Case → Repository
|
|
13
|
-
*/
|
|
14
|
-
module.exports = catchAsyncError(async (req, res, next) => {
|
|
15
|
-
const authorizationHeader = req.headers.authorization;
|
|
16
|
-
|
|
17
|
-
if (authorizationHeader) {
|
|
18
|
-
// Initialize dependencies following DDD pattern
|
|
19
|
-
const { userConfig } = loadAppDefinition();
|
|
20
|
-
const userRepository = createUserRepository({ userConfig });
|
|
21
|
-
const getUserFromBearerToken = new GetUserFromBearerToken({
|
|
22
|
-
userRepository,
|
|
23
|
-
userConfig,
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
// Execute use case to load user
|
|
28
|
-
req.user = await getUserFromBearerToken.execute(
|
|
29
|
-
authorizationHeader
|
|
30
|
-
);
|
|
31
|
-
} catch (error) {
|
|
32
|
-
// Don't fail - just leave req.user undefined
|
|
33
|
-
// Let requireLoggedInUser middleware handle auth failures
|
|
34
|
-
console.debug('Failed to load user from token:', error.message);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return next();
|
|
39
|
-
});
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Middleware to require admin API key authentication.
|
|
3
|
-
* Checks for X-API-Key header matching ADMIN_API_KEY environment variable.
|
|
4
|
-
* In non-production environments, allows all requests through for easier development.
|
|
5
|
-
*
|
|
6
|
-
* @param {import('express').Request} req - Express request object
|
|
7
|
-
* @param {import('express').Response} res - Express response object
|
|
8
|
-
* @param {import('express').NextFunction} next - Express next middleware function
|
|
9
|
-
*/
|
|
10
|
-
const requireAdmin = (req, res, next) => {
|
|
11
|
-
// Allow access in local development (when NODE_ENV is not production)
|
|
12
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
13
|
-
console.log('[requireAdmin] Development mode - bypassing admin auth');
|
|
14
|
-
return next();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const apiKey = req.headers['x-api-key'];
|
|
18
|
-
|
|
19
|
-
if (!apiKey) {
|
|
20
|
-
console.error('[requireAdmin] Missing X-API-Key header');
|
|
21
|
-
return res.status(401).json({
|
|
22
|
-
status: 'error',
|
|
23
|
-
message: 'Unauthorized - Admin API key required',
|
|
24
|
-
code: 'MISSING_API_KEY',
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (apiKey !== process.env.ADMIN_API_KEY) {
|
|
29
|
-
console.error('[requireAdmin] Invalid API key provided');
|
|
30
|
-
return res.status(401).json({
|
|
31
|
-
status: 'error',
|
|
32
|
-
message: 'Unauthorized - Invalid admin API key',
|
|
33
|
-
code: 'INVALID_API_KEY',
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
console.log('[requireAdmin] Admin authentication successful');
|
|
38
|
-
next();
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
module.exports = { requireAdmin };
|