@equilateral_ai/mindmeld 3.5.3 → 4.0.2
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/hooks/session-start.js +312 -85
- package/package.json +21 -13
- package/scripts/init-project.js +9 -23
- package/scripts/repo-analyzer.js +118 -2
- package/src/client/dbShim.js +16 -0
- package/src/core/AuthManager.js +3 -2
- package/src/handlers/helpers/dbOperations.js +9 -46
- package/src/index.js +2 -217
- package/src/utils/piiMask.js +16 -0
- package/scripts/inject.js +0 -409
- package/scripts/mcp-bridge.js +0 -220
- package/scripts/standards.js +0 -285
- package/src/collaboration/CollaborationPrompt.js +0 -460
- package/src/core/AlertEngine.js +0 -813
- package/src/core/AlertNotifier.js +0 -363
- package/src/core/CorrelationAnalyzer.js +0 -931
- package/src/core/CrossReferenceEngine.js +0 -624
- package/src/core/CurationEngine.js +0 -688
- package/src/core/DeprecationScheduler.js +0 -183
- package/src/core/LoadBearingDetector.js +0 -242
- package/src/core/NotificationService.js +0 -1032
- package/src/core/RapportOrchestrator.js +0 -632
- package/src/core/RelevanceDetector.js +0 -694
- package/src/core/StandardLifecycle.js +0 -244
- package/src/core/StandardsIngestion.js +0 -991
- package/src/core/TeamLoadBearingDetector.js +0 -431
- package/src/core/parsers/adrParser.js +0 -479
- package/src/core/parsers/cursorRulesParser.js +0 -564
- package/src/core/parsers/eslintParser.js +0 -439
- package/src/database/dbOperations.js +0 -105
- package/src/handlers/activity/activityGetMe.js +0 -98
- package/src/handlers/activity/activityGetTeam.js +0 -175
- package/src/handlers/admin/adminSetup.js +0 -216
- package/src/handlers/alerts/alertsAcknowledge.js +0 -92
- package/src/handlers/alerts/alertsGet.js +0 -250
- package/src/handlers/analytics/activitySummaryGet.js +0 -234
- package/src/handlers/analytics/coachingGet.js +0 -361
- package/src/handlers/analytics/convergenceGet.js +0 -236
- package/src/handlers/analytics/developerScoreGet.js +0 -137
- package/src/handlers/collaborators/collaboratorAdd.js +0 -200
- package/src/handlers/collaborators/collaboratorInvite.js +0 -219
- package/src/handlers/collaborators/collaboratorList.js +0 -82
- package/src/handlers/collaborators/collaboratorRemove.js +0 -128
- package/src/handlers/collaborators/inviteAccept.js +0 -122
- package/src/handlers/company/companyUsersDelete.js +0 -141
- package/src/handlers/company/companyUsersGet.js +0 -90
- package/src/handlers/company/companyUsersPost.js +0 -267
- package/src/handlers/company/companyUsersPut.js +0 -76
- package/src/handlers/context/contextGet.js +0 -57
- package/src/handlers/context/invariantsGet.js +0 -74
- package/src/handlers/context/loopsGet.js +0 -82
- package/src/handlers/context/notesCreate.js +0 -74
- package/src/handlers/context/purposeGet.js +0 -78
- package/src/handlers/correlations/correlationsDeveloperGet.js +0 -227
- package/src/handlers/correlations/correlationsGet.js +0 -93
- package/src/handlers/correlations/correlationsProjectGet.js +0 -153
- package/src/handlers/enterprise/controlTowerGet.js +0 -224
- package/src/handlers/enterprise/enterpriseAuditGet.js +0 -108
- package/src/handlers/enterprise/enterpriseContributorsGet.js +0 -85
- package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +0 -53
- package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +0 -77
- package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +0 -71
- package/src/handlers/enterprise/enterpriseKnowledgeGet.js +0 -87
- package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +0 -122
- package/src/handlers/enterprise/enterpriseOnboardingComplete.js +0 -77
- package/src/handlers/enterprise/enterpriseOnboardingInvite.js +0 -138
- package/src/handlers/enterprise/enterpriseOnboardingSetup.js +0 -128
- package/src/handlers/enterprise/enterpriseOnboardingStatus.js +0 -88
- package/src/handlers/github/githubConnectionStatus.js +0 -49
- package/src/handlers/github/githubDiscoverPatterns.js +0 -621
- package/src/handlers/github/githubOAuthCallback.js +0 -178
- package/src/handlers/github/githubOAuthStart.js +0 -59
- package/src/handlers/github/githubPatternsReview.js +0 -76
- package/src/handlers/github/githubReposList.js +0 -105
- package/src/handlers/health/healthGet.js +0 -55
- package/src/handlers/helpers/auditLogger.js +0 -201
- package/src/handlers/helpers/checkSuperAdmin.js +0 -84
- package/src/handlers/helpers/decisionFrames.js +0 -29
- package/src/handlers/helpers/errorHandler.js +0 -49
- package/src/handlers/helpers/index.js +0 -138
- package/src/handlers/helpers/lambdaWrapper.js +0 -60
- package/src/handlers/helpers/mindmeldMcpCore.js +0 -1103
- package/src/handlers/helpers/predictiveCache.js +0 -51
- package/src/handlers/helpers/projectAccess.js +0 -88
- package/src/handlers/helpers/responseUtil.js +0 -55
- package/src/handlers/helpers/subscriptionTiers.js +0 -1168
- package/src/handlers/mcp/mcpHandler.js +0 -569
- package/src/handlers/mcp/mindmeldMcpHandler.js +0 -124
- package/src/handlers/mcp/mindmeldMcpStreamHandler.js +0 -342
- package/src/handlers/notifications/getPreferences.js +0 -84
- package/src/handlers/notifications/sendNotification.js +0 -170
- package/src/handlers/notifications/updatePreferences.js +0 -316
- package/src/handlers/patterns/patternEvaluatePromotionPost.js +0 -173
- package/src/handlers/patterns/patternUsagePost.js +0 -182
- package/src/handlers/patterns/patternViolationPost.js +0 -185
- package/src/handlers/projects/projectCreate.js +0 -248
- package/src/handlers/projects/projectDelete.js +0 -82
- package/src/handlers/projects/projectGet.js +0 -95
- package/src/handlers/projects/projectUpdate.js +0 -117
- package/src/handlers/reports/aiLeverage.js +0 -210
- package/src/handlers/reports/engineeringInvestment.js +0 -132
- package/src/handlers/reports/riskForecast.js +0 -206
- package/src/handlers/reports/standardsRoi.js +0 -254
- package/src/handlers/scheduled/analyzeCorrelations.js +0 -178
- package/src/handlers/scheduled/analyzeGitHistory.js +0 -510
- package/src/handlers/scheduled/generateAlerts.js +0 -135
- package/src/handlers/scheduled/maturityUpdateJob.js +0 -166
- package/src/handlers/scheduled/refreshActivity.js +0 -21
- package/src/handlers/scheduled/scanCompliance.js +0 -334
- package/src/handlers/sessions/sessionEndPost.js +0 -180
- package/src/handlers/sessions/sessionStandardsPost.js +0 -171
- package/src/handlers/standards/catalogGet.js +0 -185
- package/src/handlers/standards/catalogSync.js +0 -120
- package/src/handlers/standards/discoveriesGet.js +0 -89
- package/src/handlers/standards/projectStandardsGet.js +0 -129
- package/src/handlers/standards/projectStandardsPut.js +0 -151
- package/src/handlers/standards/standardsAuditGet.js +0 -65
- package/src/handlers/standards/standardsParseUpload.js +0 -149
- package/src/handlers/standards/standardsRelevantPost.js +0 -405
- package/src/handlers/standards/standardsTransition.js +0 -161
- package/src/handlers/stripe/addonManagePost.js +0 -240
- package/src/handlers/stripe/billingPortalPost.js +0 -93
- package/src/handlers/stripe/enterpriseCheckoutPost.js +0 -272
- package/src/handlers/stripe/seatsUpdatePost.js +0 -185
- package/src/handlers/stripe/subscriptionCancelDelete.js +0 -169
- package/src/handlers/stripe/subscriptionCreatePost.js +0 -221
- package/src/handlers/stripe/subscriptionUpdatePut.js +0 -163
- package/src/handlers/stripe/webhookPost.js +0 -482
- package/src/handlers/user/apiTokenCreate.js +0 -71
- package/src/handlers/user/apiTokenList.js +0 -64
- package/src/handlers/user/userSplashAck.js +0 -91
- package/src/handlers/user/userSplashGet.js +0 -211
- package/src/handlers/users/cognitoPostConfirmation.js +0 -186
- package/src/handlers/users/cognitoPreSignUp.js +0 -114
- package/src/handlers/users/userEntitlementsGet.js +0 -89
- package/src/handlers/users/userGet.js +0 -118
- package/src/handlers/users/userProfilePut.js +0 -77
- package/src/handlers/webhooks/githubWebhook.js +0 -215
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cognito PreSignUp Handler
|
|
3
|
-
* Blocks bot registrations before user creation in Cognito
|
|
4
|
-
* Auto-confirms Google OAuth users and admin-invited users
|
|
5
|
-
*
|
|
6
|
-
* Triggered by: Cognito User Pool pre-signup trigger
|
|
7
|
-
*
|
|
8
|
-
* Note: Cognito trigger must be configured manually in Cognito console
|
|
9
|
-
* as SAM cannot reference external user pools
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const { executeQuery } = require('./helpers');
|
|
13
|
-
|
|
14
|
-
const DISPOSABLE_DOMAINS = new Set([
|
|
15
|
-
'mailinator.com', 'guerrillamail.com', 'tempmail.com',
|
|
16
|
-
'throwaway.email', 'yopmail.com', 'sharklasers.com',
|
|
17
|
-
'guerrillamailblock.com', 'grr.la', 'maildrop.cc',
|
|
18
|
-
'dispostable.com', 'temp-mail.org', '10minutemail.com',
|
|
19
|
-
'trashmail.com', 'fakeinbox.com', 'mailnesia.com',
|
|
20
|
-
'tempinbox.com', 'mailcatch.com', 'throwam.com'
|
|
21
|
-
]);
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Detect dot-stuffed Gmail addresses used by signup bots
|
|
25
|
-
* Gmail ignores dots in the local part, so bots insert random dots
|
|
26
|
-
* to generate unique-looking addresses that all deliver to the same inbox
|
|
27
|
-
*/
|
|
28
|
-
function isDotStuffedGmail(email) {
|
|
29
|
-
const atIndex = email.lastIndexOf('@');
|
|
30
|
-
if (atIndex === -1) return false;
|
|
31
|
-
|
|
32
|
-
const localPart = email.substring(0, atIndex).toLowerCase();
|
|
33
|
-
const domain = email.substring(atIndex + 1).toLowerCase();
|
|
34
|
-
|
|
35
|
-
if (domain !== 'gmail.com' && domain !== 'googlemail.com') return false;
|
|
36
|
-
|
|
37
|
-
const dotCount = (localPart.match(/\./g) || []).length;
|
|
38
|
-
const cleanLength = localPart.replace(/\./g, '').length;
|
|
39
|
-
|
|
40
|
-
if (cleanLength === 0) return true;
|
|
41
|
-
|
|
42
|
-
// 4+ dots in a short local part is a strong bot signal
|
|
43
|
-
if (dotCount >= 4 && cleanLength < 20) return true;
|
|
44
|
-
|
|
45
|
-
// More than 30% dots relative to actual characters
|
|
46
|
-
if (dotCount > 0 && dotCount / cleanLength > 0.3) return true;
|
|
47
|
-
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Check if email uses a known disposable email domain
|
|
53
|
-
*/
|
|
54
|
-
function isDisposableEmail(email) {
|
|
55
|
-
const domain = email.toLowerCase().split('@')[1];
|
|
56
|
-
return DISPOSABLE_DOMAINS.has(domain);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async function handler(event) {
|
|
60
|
-
console.log('PreSignUp trigger:', JSON.stringify({
|
|
61
|
-
triggerSource: event.triggerSource,
|
|
62
|
-
userName: event.userName,
|
|
63
|
-
email: event.request?.userAttributes?.email
|
|
64
|
-
}));
|
|
65
|
-
|
|
66
|
-
// Auto-confirm external provider signups (Google OAuth)
|
|
67
|
-
// Google provides verified email — no Cognito verification needed
|
|
68
|
-
if (event.triggerSource === 'PreSignUp_ExternalProvider') {
|
|
69
|
-
console.log(`[PreSignUp] Auto-confirming external provider user: ${event.request?.userAttributes?.email}`);
|
|
70
|
-
event.response.autoConfirmUser = true;
|
|
71
|
-
event.response.autoVerifyEmail = true;
|
|
72
|
-
return event;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Only validate user-initiated signups (not admin-created)
|
|
76
|
-
if (event.triggerSource !== 'PreSignUp_SignUp') {
|
|
77
|
-
return event;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const email = event.request?.userAttributes?.email;
|
|
81
|
-
if (!email) {
|
|
82
|
-
throw new Error('Email address is required for registration');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (isDisposableEmail(email)) {
|
|
86
|
-
console.log(`[PreSignUp] Blocked disposable email: ${email}`);
|
|
87
|
-
throw new Error('Please use a permanent email address to register.');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (isDotStuffedGmail(email)) {
|
|
91
|
-
console.log(`[PreSignUp] Blocked dot-stuffed Gmail: ${email}`);
|
|
92
|
-
throw new Error('This email address format is not accepted. Please use your primary email address.');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Auto-confirm users who have been invited (entitlement already exists)
|
|
96
|
-
try {
|
|
97
|
-
const result = await executeQuery(
|
|
98
|
-
'SELECT 1 FROM rapport.user_entitlements WHERE email_address = $1 LIMIT 1',
|
|
99
|
-
[email.toLowerCase()]
|
|
100
|
-
);
|
|
101
|
-
if (result.rows.length > 0) {
|
|
102
|
-
console.log(`[PreSignUp] Auto-confirming invited user: ${email}`);
|
|
103
|
-
event.response.autoConfirmUser = true;
|
|
104
|
-
event.response.autoVerifyEmail = true;
|
|
105
|
-
}
|
|
106
|
-
} catch (err) {
|
|
107
|
-
// DB check failed — don't block signup, just skip auto-confirm
|
|
108
|
-
console.error(`[PreSignUp] Entitlement check failed for ${email}:`, err.message);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return event;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
module.exports = { handler };
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User Entitlements Get Handler
|
|
3
|
-
* Retrieves user's company/project access rights
|
|
4
|
-
*
|
|
5
|
-
* GET /api/users/entitlements
|
|
6
|
-
* Auth: Cognito JWT required
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Get current user's entitlements
|
|
13
|
-
*/
|
|
14
|
-
async function getEntitlements({ requestContext }) {
|
|
15
|
-
try {
|
|
16
|
-
const Request_ID = requestContext.requestId;
|
|
17
|
-
// REST API: requestContext.authorizer.claims.email
|
|
18
|
-
// HTTP API: requestContext.authorizer.jwt.claims.email
|
|
19
|
-
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
20
|
-
|
|
21
|
-
if (!email) {
|
|
22
|
-
return createErrorResponse(401, 'Authentication required');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Get all entitlements with company and client details
|
|
26
|
-
const query = `
|
|
27
|
-
SELECT
|
|
28
|
-
ue.email_address,
|
|
29
|
-
ue.client_id,
|
|
30
|
-
c.client_name,
|
|
31
|
-
c.subscription_tier,
|
|
32
|
-
ue.company_id,
|
|
33
|
-
co.company_name,
|
|
34
|
-
ue.admin,
|
|
35
|
-
ue.member,
|
|
36
|
-
ue.create_date
|
|
37
|
-
FROM rapport.user_entitlements ue
|
|
38
|
-
JOIN rapport.clients c ON ue.client_id = c.client_id
|
|
39
|
-
JOIN rapport.companies co ON ue.company_id = co.company_id
|
|
40
|
-
WHERE ue.email_address = $1
|
|
41
|
-
AND c.active = true
|
|
42
|
-
AND co.active = true
|
|
43
|
-
ORDER BY c.client_name, co.company_name
|
|
44
|
-
`;
|
|
45
|
-
|
|
46
|
-
const result = await executeQuery(query, [email]);
|
|
47
|
-
|
|
48
|
-
// Group by client
|
|
49
|
-
const clientMap = {};
|
|
50
|
-
for (const row of result.rows) {
|
|
51
|
-
if (!clientMap[row.client_id]) {
|
|
52
|
-
clientMap[row.client_id] = {
|
|
53
|
-
client_id: row.client_id,
|
|
54
|
-
client_name: row.client_name,
|
|
55
|
-
subscription_tier: row.subscription_tier,
|
|
56
|
-
companies: []
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
clientMap[row.client_id].companies.push({
|
|
60
|
-
company_id: row.company_id,
|
|
61
|
-
company_name: row.company_name,
|
|
62
|
-
admin: row.admin,
|
|
63
|
-
member: row.member,
|
|
64
|
-
since: row.create_date
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const clients = Object.values(clientMap);
|
|
69
|
-
|
|
70
|
-
return createSuccessResponse(
|
|
71
|
-
{
|
|
72
|
-
Records: clients
|
|
73
|
-
},
|
|
74
|
-
'Entitlements retrieved',
|
|
75
|
-
{
|
|
76
|
-
Total_Records: clients.length,
|
|
77
|
-
Total_Companies: result.rowCount,
|
|
78
|
-
Request_ID,
|
|
79
|
-
Timestamp: new Date().toISOString()
|
|
80
|
-
}
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
} catch (error) {
|
|
84
|
-
console.error('Handler Error:', error);
|
|
85
|
-
return handleError(error);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
exports.handler = wrapHandler(getEntitlements);
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User Get Handler
|
|
3
|
-
* Retrieves user profile and subscription info
|
|
4
|
-
*
|
|
5
|
-
* GET /api/users/me
|
|
6
|
-
* Auth: Cognito JWT required
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError, getTierConfig } = require('./helpers');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Get current user's profile
|
|
13
|
-
*/
|
|
14
|
-
async function getUser({ requestContext }) {
|
|
15
|
-
try {
|
|
16
|
-
const Request_ID = requestContext.requestId;
|
|
17
|
-
// REST API: requestContext.authorizer.claims.email
|
|
18
|
-
// HTTP API: requestContext.authorizer.jwt.claims.email
|
|
19
|
-
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
20
|
-
|
|
21
|
-
if (!email) {
|
|
22
|
-
return createErrorResponse(401, 'Authentication required');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Get user with their primary client subscription and company
|
|
26
|
-
const query = `
|
|
27
|
-
SELECT
|
|
28
|
-
u.email_address,
|
|
29
|
-
u.first_name,
|
|
30
|
-
u.last_name,
|
|
31
|
-
u.client_id,
|
|
32
|
-
u.user_status,
|
|
33
|
-
u.create_date,
|
|
34
|
-
c.client_name,
|
|
35
|
-
c.subscription_tier,
|
|
36
|
-
c.subscription_status,
|
|
37
|
-
c.subscription_ends_at,
|
|
38
|
-
c.stripe_customer_id,
|
|
39
|
-
ue.company_id
|
|
40
|
-
FROM rapport.users u
|
|
41
|
-
LEFT JOIN rapport.clients c ON u.client_id = c.client_id
|
|
42
|
-
LEFT JOIN rapport.user_entitlements ue ON u.email_address = ue.email_address
|
|
43
|
-
WHERE u.email_address = $1
|
|
44
|
-
AND u.active = true
|
|
45
|
-
LIMIT 1
|
|
46
|
-
`;
|
|
47
|
-
|
|
48
|
-
const result = await executeQuery(query, [email]);
|
|
49
|
-
|
|
50
|
-
if (result.rowCount === 0) {
|
|
51
|
-
return createErrorResponse(404, 'User not found');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const user = result.rows[0];
|
|
55
|
-
const tierConfig = getTierConfig(user.subscription_tier || 'free');
|
|
56
|
-
|
|
57
|
-
// Get usage counts scoped to user's entitled companies
|
|
58
|
-
const usageQuery = `
|
|
59
|
-
SELECT
|
|
60
|
-
(SELECT COUNT(*) FROM rapport.user_entitlements WHERE client_id = $1) as collaborators,
|
|
61
|
-
(SELECT COUNT(*) FROM rapport.projects p
|
|
62
|
-
JOIN rapport.user_entitlements ue ON p.company_id = ue.company_id
|
|
63
|
-
WHERE ue.email_address = $2 AND p.archived = false) as projects,
|
|
64
|
-
(SELECT COUNT(*) FROM rapport.invariants i
|
|
65
|
-
JOIN rapport.projects p ON i.project_id = p.project_id
|
|
66
|
-
JOIN rapport.user_entitlements ue ON p.company_id = ue.company_id
|
|
67
|
-
WHERE ue.email_address = $2) as invariants
|
|
68
|
-
`;
|
|
69
|
-
|
|
70
|
-
const usageResult = await executeQuery(usageQuery, [user.client_id, email]);
|
|
71
|
-
const usage = usageResult.rows[0];
|
|
72
|
-
|
|
73
|
-
return createSuccessResponse(
|
|
74
|
-
{
|
|
75
|
-
Records: [{
|
|
76
|
-
email_address: user.email_address,
|
|
77
|
-
first_name: user.first_name,
|
|
78
|
-
last_name: user.last_name,
|
|
79
|
-
client_id: user.client_id,
|
|
80
|
-
company_id: user.company_id,
|
|
81
|
-
client_name: user.client_name,
|
|
82
|
-
user_status: user.user_status,
|
|
83
|
-
member_since: user.create_date,
|
|
84
|
-
subscription: {
|
|
85
|
-
tier: user.subscription_tier || 'free',
|
|
86
|
-
tier_name: tierConfig?.displayName || 'Free',
|
|
87
|
-
status: user.subscription_status || 'active',
|
|
88
|
-
ends_at: user.subscription_ends_at,
|
|
89
|
-
has_stripe: !!user.stripe_customer_id,
|
|
90
|
-
features: tierConfig?.features || []
|
|
91
|
-
},
|
|
92
|
-
usage: {
|
|
93
|
-
collaborators: parseInt(usage.collaborators) || 0,
|
|
94
|
-
projects: parseInt(usage.projects) || 0,
|
|
95
|
-
invariants: parseInt(usage.invariants) || 0
|
|
96
|
-
},
|
|
97
|
-
limits: {
|
|
98
|
-
max_collaborators: tierConfig?.maxCollaborators ?? null,
|
|
99
|
-
max_projects: tierConfig?.maxProjects ?? null,
|
|
100
|
-
max_invariants: tierConfig?.maxInvariants ?? null
|
|
101
|
-
}
|
|
102
|
-
}]
|
|
103
|
-
},
|
|
104
|
-
'User profile retrieved',
|
|
105
|
-
{
|
|
106
|
-
Total_Records: 1,
|
|
107
|
-
Request_ID,
|
|
108
|
-
Timestamp: new Date().toISOString()
|
|
109
|
-
}
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
} catch (error) {
|
|
113
|
-
console.error('Handler Error:', error);
|
|
114
|
-
return handleError(error);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
exports.handler = wrapHandler(getUser);
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User Profile Update Handler
|
|
3
|
-
* Updates user display name (first_name, last_name)
|
|
4
|
-
*
|
|
5
|
-
* PUT /api/users/profile
|
|
6
|
-
* Auth: Cognito JWT required
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Update current user's profile
|
|
13
|
-
*/
|
|
14
|
-
async function updateProfile({ body, requestContext }) {
|
|
15
|
-
try {
|
|
16
|
-
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
17
|
-
|
|
18
|
-
if (!email) {
|
|
19
|
-
return createErrorResponse(401, 'Authentication required');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const { first_name, last_name } = JSON.parse(body || '{}');
|
|
23
|
-
|
|
24
|
-
if (!first_name && !last_name) {
|
|
25
|
-
return createErrorResponse(400, 'At least one of first_name or last_name is required');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Build dynamic update
|
|
29
|
-
const setClauses = [];
|
|
30
|
-
const params = [];
|
|
31
|
-
let paramIndex = 1;
|
|
32
|
-
|
|
33
|
-
if (first_name !== undefined) {
|
|
34
|
-
setClauses.push(`first_name = $${paramIndex++}`);
|
|
35
|
-
params.push(first_name.trim().substring(0, 100));
|
|
36
|
-
}
|
|
37
|
-
if (last_name !== undefined) {
|
|
38
|
-
setClauses.push(`last_name = $${paramIndex++}`);
|
|
39
|
-
params.push(last_name.trim().substring(0, 100));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
setClauses.push(`last_updated = NOW()`);
|
|
43
|
-
params.push(email);
|
|
44
|
-
|
|
45
|
-
const query = `
|
|
46
|
-
UPDATE rapport.users
|
|
47
|
-
SET ${setClauses.join(', ')}
|
|
48
|
-
WHERE email_address = $${paramIndex}
|
|
49
|
-
AND active = true
|
|
50
|
-
RETURNING email_address, first_name, last_name
|
|
51
|
-
`;
|
|
52
|
-
|
|
53
|
-
const result = await executeQuery(query, params);
|
|
54
|
-
|
|
55
|
-
if (result.rowCount === 0) {
|
|
56
|
-
return createErrorResponse(404, 'User not found');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const user = result.rows[0];
|
|
60
|
-
|
|
61
|
-
return createSuccessResponse(
|
|
62
|
-
{
|
|
63
|
-
email_address: user.email_address,
|
|
64
|
-
first_name: user.first_name,
|
|
65
|
-
last_name: user.last_name,
|
|
66
|
-
display_name: [user.first_name, user.last_name].filter(Boolean).join(' ')
|
|
67
|
-
},
|
|
68
|
-
'Profile updated'
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
} catch (error) {
|
|
72
|
-
console.error('Handler Error:', error);
|
|
73
|
-
return handleError(error);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
exports.handler = wrapHandler(updateProfile);
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GitHub Webhook Handler
|
|
3
|
-
* Receives push and pull_request events to track commits and PRs
|
|
4
|
-
*
|
|
5
|
-
* POST /api/webhooks/github
|
|
6
|
-
* Auth: GitHub webhook signature verification (no Cognito)
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const crypto = require('crypto');
|
|
10
|
-
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
11
|
-
|
|
12
|
-
exports.handler = wrapHandler(async ({ body, headers }) => {
|
|
13
|
-
// Verify webhook signature — try per-repo secret first, then global fallback
|
|
14
|
-
const signature = headers['x-hub-signature-256'] || headers['X-Hub-Signature-256'];
|
|
15
|
-
const rawBody = JSON.stringify(body);
|
|
16
|
-
const repoFullName = body?.repository?.full_name;
|
|
17
|
-
|
|
18
|
-
let verified = false;
|
|
19
|
-
|
|
20
|
-
// Try per-repo secret from git_repositories
|
|
21
|
-
if (repoFullName) {
|
|
22
|
-
const repoResult = await executeQuery(`
|
|
23
|
-
SELECT webhook_secret FROM rapport.git_repositories
|
|
24
|
-
WHERE repo_name = $1 AND webhook_secret IS NOT NULL
|
|
25
|
-
LIMIT 1
|
|
26
|
-
`, [repoFullName]);
|
|
27
|
-
|
|
28
|
-
if (repoResult.rowCount > 0 && repoResult.rows[0].webhook_secret) {
|
|
29
|
-
verified = verifySignature(rawBody, signature, repoResult.rows[0].webhook_secret);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Fallback to global secret
|
|
34
|
-
if (!verified) {
|
|
35
|
-
const globalSecret = process.env.GITHUB_WEBHOOK_SECRET;
|
|
36
|
-
if (globalSecret) {
|
|
37
|
-
verified = verifySignature(rawBody, signature, globalSecret);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (!verified) {
|
|
42
|
-
return createErrorResponse(401, 'Invalid signature');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const eventType = headers['x-github-event'] || headers['X-GitHub-Event'];
|
|
46
|
-
|
|
47
|
-
if (eventType === 'push') {
|
|
48
|
-
await handlePushEvent(body);
|
|
49
|
-
} else if (eventType === 'pull_request') {
|
|
50
|
-
await handlePREvent(body);
|
|
51
|
-
} else if (eventType === 'ping') {
|
|
52
|
-
// GitHub sends ping when webhook is first configured
|
|
53
|
-
return createSuccessResponse({ received: true }, 'Webhook configured');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return createSuccessResponse(
|
|
57
|
-
{ received: true, event: eventType },
|
|
58
|
-
'Webhook processed'
|
|
59
|
-
);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
function verifySignature(payload, signature, secret) {
|
|
63
|
-
if (!signature) return false;
|
|
64
|
-
|
|
65
|
-
const expectedSignature = 'sha256=' + crypto
|
|
66
|
-
.createHmac('sha256', secret)
|
|
67
|
-
.update(payload)
|
|
68
|
-
.digest('hex');
|
|
69
|
-
|
|
70
|
-
return crypto.timingSafeEqual(
|
|
71
|
-
Buffer.from(signature),
|
|
72
|
-
Buffer.from(expectedSignature)
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function handlePushEvent(payload) {
|
|
77
|
-
const repoFullName = payload.repository?.full_name;
|
|
78
|
-
if (!repoFullName) return;
|
|
79
|
-
|
|
80
|
-
const repoUrl = payload.repository?.html_url || `https://github.com/${repoFullName}`;
|
|
81
|
-
const branch = payload.ref?.replace('refs/heads/', '') || 'unknown';
|
|
82
|
-
|
|
83
|
-
// Find or create repo in git_repositories
|
|
84
|
-
let repoResult = await executeQuery(`
|
|
85
|
-
SELECT repo_id, company_id FROM rapport.git_repositories
|
|
86
|
-
WHERE repo_name = $1 OR repo_url = $2
|
|
87
|
-
`, [repoFullName, repoUrl]);
|
|
88
|
-
|
|
89
|
-
if (repoResult.rowCount === 0) {
|
|
90
|
-
// Look up company from projects table
|
|
91
|
-
const projectResult = await executeQuery(`
|
|
92
|
-
SELECT project_id, company_id FROM rapport.projects
|
|
93
|
-
WHERE repo_url LIKE $1
|
|
94
|
-
LIMIT 1
|
|
95
|
-
`, [`%${repoFullName}%`]);
|
|
96
|
-
|
|
97
|
-
const companyId = projectResult.rows[0]?.company_id || null;
|
|
98
|
-
|
|
99
|
-
// Auto-register the repo
|
|
100
|
-
repoResult = await executeQuery(`
|
|
101
|
-
INSERT INTO rapport.git_repositories (repo_id, repo_name, repo_url, company_id, default_branch, created_at, updated_at)
|
|
102
|
-
VALUES (gen_random_uuid(), $1, $2, $3, $4, NOW(), NOW())
|
|
103
|
-
RETURNING repo_id, company_id
|
|
104
|
-
`, [repoFullName, repoUrl, companyId, branch]);
|
|
105
|
-
|
|
106
|
-
console.log(`Auto-registered repo: ${repoFullName} (repo_id: ${repoResult.rows[0].repo_id})`);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const repoId = repoResult.rows[0].repo_id;
|
|
110
|
-
|
|
111
|
-
for (const commit of (payload.commits || [])) {
|
|
112
|
-
await recordCommit(commit, repoId, repoFullName, repoUrl);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Update last_commit_sha on the repo
|
|
116
|
-
const lastCommit = payload.head_commit?.id || payload.commits?.[payload.commits.length - 1]?.id;
|
|
117
|
-
if (lastCommit) {
|
|
118
|
-
await executeQuery(`
|
|
119
|
-
UPDATE rapport.git_repositories SET last_commit_sha = $1, updated_at = NOW()
|
|
120
|
-
WHERE repo_id = $2
|
|
121
|
-
`, [lastCommit, repoId]);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async function recordCommit(commit, repoId, repoName, repoUrl) {
|
|
126
|
-
const authorEmail = commit.author?.email;
|
|
127
|
-
if (!authorEmail) return;
|
|
128
|
-
|
|
129
|
-
// Check for duplicate
|
|
130
|
-
const existing = await executeQuery(`
|
|
131
|
-
SELECT 1 FROM rapport.commits WHERE commit_sha = $1 LIMIT 1
|
|
132
|
-
`, [commit.id]);
|
|
133
|
-
|
|
134
|
-
if (existing.rowCount > 0) {
|
|
135
|
-
console.log(`Commit ${commit.id.substring(0, 8)} already recorded, skipping`);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Build changed_files JSONB from GitHub payload
|
|
140
|
-
const changedFiles = [
|
|
141
|
-
...(commit.added || []).map(f => ({ path: f, action: 'added' })),
|
|
142
|
-
...(commit.modified || []).map(f => ({ path: f, action: 'modified' })),
|
|
143
|
-
...(commit.removed || []).map(f => ({ path: f, action: 'removed' }))
|
|
144
|
-
];
|
|
145
|
-
|
|
146
|
-
await executeQuery(`
|
|
147
|
-
INSERT INTO rapport.commits (
|
|
148
|
-
commit_id, commit_sha, repo_id, repo_url, repo_name,
|
|
149
|
-
author_email, author_name, commit_timestamp, message,
|
|
150
|
-
changed_files, is_merge, created_at
|
|
151
|
-
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, NOW())
|
|
152
|
-
`, [
|
|
153
|
-
commit.id,
|
|
154
|
-
commit.id,
|
|
155
|
-
repoId,
|
|
156
|
-
repoUrl,
|
|
157
|
-
repoName,
|
|
158
|
-
authorEmail,
|
|
159
|
-
commit.author?.name || authorEmail,
|
|
160
|
-
commit.timestamp,
|
|
161
|
-
commit.message?.substring(0, 2000),
|
|
162
|
-
JSON.stringify(changedFiles),
|
|
163
|
-
commit.message?.toLowerCase().startsWith('merge') || false
|
|
164
|
-
]);
|
|
165
|
-
|
|
166
|
-
console.log(`Recorded commit ${commit.id.substring(0, 8)} by ${authorEmail}`);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async function handlePREvent(payload) {
|
|
170
|
-
const pr = payload.pull_request;
|
|
171
|
-
const repoFullName = payload.repository?.full_name;
|
|
172
|
-
if (!pr || !repoFullName) return;
|
|
173
|
-
|
|
174
|
-
const repoUrl = payload.repository?.html_url || `https://github.com/${repoFullName}`;
|
|
175
|
-
|
|
176
|
-
// Find repo
|
|
177
|
-
const repoResult = await executeQuery(`
|
|
178
|
-
SELECT repo_id FROM rapport.git_repositories
|
|
179
|
-
WHERE repo_name = $1 OR repo_url = $2
|
|
180
|
-
`, [repoFullName, repoUrl]);
|
|
181
|
-
|
|
182
|
-
if (repoResult.rowCount === 0) {
|
|
183
|
-
console.log(`Repo not found for: ${repoFullName} — push event must arrive first`);
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const repoId = repoResult.rows[0].repo_id;
|
|
188
|
-
const authorEmail = pr.user?.email || `${pr.user?.login}@users.noreply.github.com`;
|
|
189
|
-
const status = pr.merged ? 'merged' : pr.state;
|
|
190
|
-
|
|
191
|
-
// Upsert PR by repo_id + pr_number
|
|
192
|
-
await executeQuery(`
|
|
193
|
-
INSERT INTO rapport.pull_requests (
|
|
194
|
-
pr_id, repo_id, pr_number, title, author_email,
|
|
195
|
-
status, created_at, merged_at, closed_at,
|
|
196
|
-
lines_added, lines_removed, files_changed, review_comments
|
|
197
|
-
) VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
198
|
-
ON CONFLICT (pr_id) DO NOTHING
|
|
199
|
-
`, [
|
|
200
|
-
repoId,
|
|
201
|
-
pr.number,
|
|
202
|
-
pr.title?.substring(0, 500),
|
|
203
|
-
authorEmail,
|
|
204
|
-
status,
|
|
205
|
-
pr.created_at,
|
|
206
|
-
pr.merged_at || null,
|
|
207
|
-
pr.closed_at || null,
|
|
208
|
-
pr.additions || 0,
|
|
209
|
-
pr.deletions || 0,
|
|
210
|
-
pr.changed_files || 0,
|
|
211
|
-
pr.review_comments || 0
|
|
212
|
-
]);
|
|
213
|
-
|
|
214
|
-
console.log(`Recorded PR #${pr.number} (${status}) for ${authorEmail}`);
|
|
215
|
-
}
|