@equilateral_ai/mindmeld 3.0.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/README.md +300 -0
- package/hooks/README.md +494 -0
- package/hooks/pre-compact.js +392 -0
- package/hooks/session-start.js +264 -0
- package/package.json +90 -0
- package/scripts/harvest.js +561 -0
- package/scripts/init-project.js +437 -0
- package/scripts/inject.js +388 -0
- package/src/collaboration/CollaborationPrompt.js +460 -0
- package/src/core/AlertEngine.js +813 -0
- package/src/core/AlertNotifier.js +363 -0
- package/src/core/CorrelationAnalyzer.js +774 -0
- package/src/core/CurationEngine.js +688 -0
- package/src/core/LLMPatternDetector.js +508 -0
- package/src/core/LoadBearingDetector.js +242 -0
- package/src/core/NotificationService.js +1032 -0
- package/src/core/PatternValidator.js +355 -0
- package/src/core/README.md +160 -0
- package/src/core/RapportOrchestrator.js +446 -0
- package/src/core/RelevanceDetector.js +577 -0
- package/src/core/StandardsIngestion.js +575 -0
- package/src/core/TeamLoadBearingDetector.js +431 -0
- package/src/database/dbOperations.js +105 -0
- package/src/handlers/activity/activityGetMe.js +98 -0
- package/src/handlers/activity/activityGetTeam.js +130 -0
- package/src/handlers/alerts/alertsAcknowledge.js +91 -0
- package/src/handlers/alerts/alertsGet.js +250 -0
- package/src/handlers/collaborators/collaboratorAdd.js +201 -0
- package/src/handlers/collaborators/collaboratorInvite.js +218 -0
- package/src/handlers/collaborators/collaboratorList.js +88 -0
- package/src/handlers/collaborators/collaboratorRemove.js +127 -0
- package/src/handlers/collaborators/inviteAccept.js +122 -0
- package/src/handlers/context/contextGet.js +57 -0
- package/src/handlers/context/invariantsGet.js +74 -0
- package/src/handlers/context/loopsGet.js +82 -0
- package/src/handlers/context/notesCreate.js +74 -0
- package/src/handlers/context/purposeGet.js +78 -0
- package/src/handlers/correlations/correlationsDeveloperGet.js +226 -0
- package/src/handlers/correlations/correlationsGet.js +93 -0
- package/src/handlers/correlations/correlationsProjectGet.js +161 -0
- package/src/handlers/github/githubConnectionStatus.js +49 -0
- package/src/handlers/github/githubDiscoverPatterns.js +364 -0
- package/src/handlers/github/githubOAuthCallback.js +166 -0
- package/src/handlers/github/githubOAuthStart.js +59 -0
- package/src/handlers/github/githubPatternsReview.js +109 -0
- package/src/handlers/github/githubReposList.js +105 -0
- package/src/handlers/helpers/checkSuperAdmin.js +85 -0
- package/src/handlers/helpers/dbOperations.js +53 -0
- package/src/handlers/helpers/errorHandler.js +49 -0
- package/src/handlers/helpers/index.js +106 -0
- package/src/handlers/helpers/lambdaWrapper.js +60 -0
- package/src/handlers/helpers/responseUtil.js +55 -0
- package/src/handlers/helpers/subscriptionTiers.js +1168 -0
- package/src/handlers/notifications/getPreferences.js +84 -0
- package/src/handlers/notifications/sendNotification.js +170 -0
- package/src/handlers/notifications/updatePreferences.js +316 -0
- package/src/handlers/patterns/patternUsagePost.js +182 -0
- package/src/handlers/patterns/patternViolationPost.js +185 -0
- package/src/handlers/projects/projectCreate.js +107 -0
- package/src/handlers/projects/projectDelete.js +82 -0
- package/src/handlers/projects/projectGet.js +95 -0
- package/src/handlers/projects/projectUpdate.js +118 -0
- package/src/handlers/reports/aiLeverage.js +206 -0
- package/src/handlers/reports/engineeringInvestment.js +132 -0
- package/src/handlers/reports/riskForecast.js +186 -0
- package/src/handlers/reports/standardsRoi.js +162 -0
- package/src/handlers/scheduled/analyzeCorrelations.js +178 -0
- package/src/handlers/scheduled/analyzeGitHistory.js +510 -0
- package/src/handlers/scheduled/generateAlerts.js +135 -0
- package/src/handlers/scheduled/refreshActivity.js +21 -0
- package/src/handlers/scheduled/scanCompliance.js +334 -0
- package/src/handlers/sessions/sessionEndPost.js +180 -0
- package/src/handlers/sessions/sessionStandardsPost.js +135 -0
- package/src/handlers/stripe/addonManagePost.js +240 -0
- package/src/handlers/stripe/billingPortalPost.js +93 -0
- package/src/handlers/stripe/enterpriseCheckoutPost.js +272 -0
- package/src/handlers/stripe/seatsUpdatePost.js +185 -0
- package/src/handlers/stripe/subscriptionCancelDelete.js +169 -0
- package/src/handlers/stripe/subscriptionCreatePost.js +221 -0
- package/src/handlers/stripe/subscriptionUpdatePut.js +163 -0
- package/src/handlers/stripe/webhookPost.js +454 -0
- package/src/handlers/users/cognitoPostConfirmation.js +150 -0
- package/src/handlers/users/userEntitlementsGet.js +89 -0
- package/src/handlers/users/userGet.js +114 -0
- package/src/handlers/webhooks/githubWebhook.js +223 -0
- package/src/index.js +969 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cognito Post-Confirmation Handler
|
|
3
|
+
* Creates user record and personal workspace after email verification
|
|
4
|
+
*
|
|
5
|
+
* Triggered by: Cognito User Pool post-confirmation trigger
|
|
6
|
+
* Following: cognito_authentication_standards.md
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { executeQuery } = require('./helpers');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Handle post-confirmation trigger
|
|
13
|
+
* Creates user, personal client, personal company, and entitlement
|
|
14
|
+
*/
|
|
15
|
+
async function handler(event, context) {
|
|
16
|
+
console.log('Post-confirmation trigger:', JSON.stringify(event, null, 2));
|
|
17
|
+
|
|
18
|
+
// Only process SignUp confirmations
|
|
19
|
+
if (event.triggerSource !== 'PostConfirmation_ConfirmSignUp') {
|
|
20
|
+
console.log('Skipping trigger source:', event.triggerSource);
|
|
21
|
+
return event;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { userName, request } = event;
|
|
25
|
+
const { userAttributes } = request;
|
|
26
|
+
|
|
27
|
+
const email = userAttributes.email;
|
|
28
|
+
const cognitoSub = userAttributes.sub;
|
|
29
|
+
const firstName = userAttributes.given_name || '';
|
|
30
|
+
const lastName = userAttributes.family_name || '';
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
await executeQuery('BEGIN');
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
// 1. Create user record
|
|
37
|
+
const userQuery = `
|
|
38
|
+
INSERT INTO rapport.users (
|
|
39
|
+
email_address,
|
|
40
|
+
cognito_sub,
|
|
41
|
+
first_name,
|
|
42
|
+
last_name,
|
|
43
|
+
client_id,
|
|
44
|
+
user_status,
|
|
45
|
+
active
|
|
46
|
+
)
|
|
47
|
+
VALUES ($1, $2, $3, $4, $5, 'Active', true)
|
|
48
|
+
ON CONFLICT (email_address) DO UPDATE SET
|
|
49
|
+
cognito_sub = EXCLUDED.cognito_sub,
|
|
50
|
+
first_name = EXCLUDED.first_name,
|
|
51
|
+
last_name = EXCLUDED.last_name,
|
|
52
|
+
last_updated = CURRENT_TIMESTAMP
|
|
53
|
+
RETURNING email_address
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
// Personal client ID based on email (sanitized)
|
|
57
|
+
const personalClientId = `personal_${email.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()}`;
|
|
58
|
+
|
|
59
|
+
await executeQuery(userQuery, [
|
|
60
|
+
email,
|
|
61
|
+
cognitoSub,
|
|
62
|
+
firstName,
|
|
63
|
+
lastName,
|
|
64
|
+
personalClientId
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
console.log('User created/updated:', email);
|
|
68
|
+
|
|
69
|
+
// 2. Create personal client (free tier)
|
|
70
|
+
const clientQuery = `
|
|
71
|
+
INSERT INTO rapport.clients (
|
|
72
|
+
client_id,
|
|
73
|
+
client_name,
|
|
74
|
+
client_type,
|
|
75
|
+
client_status,
|
|
76
|
+
subscription_tier,
|
|
77
|
+
subscription_status
|
|
78
|
+
)
|
|
79
|
+
VALUES ($1, $2, 'PERSONAL', 'Active', 'free', 'active')
|
|
80
|
+
ON CONFLICT (client_id) DO NOTHING
|
|
81
|
+
RETURNING client_id
|
|
82
|
+
`;
|
|
83
|
+
|
|
84
|
+
const clientName = firstName && lastName
|
|
85
|
+
? `${firstName} ${lastName}'s Workspace`
|
|
86
|
+
: `${email.split('@')[0]}'s Workspace`;
|
|
87
|
+
|
|
88
|
+
await executeQuery(clientQuery, [personalClientId, clientName]);
|
|
89
|
+
|
|
90
|
+
console.log('Personal client created:', personalClientId);
|
|
91
|
+
|
|
92
|
+
// 3. Create personal company
|
|
93
|
+
const companyId = `${personalClientId}_main`;
|
|
94
|
+
const companyQuery = `
|
|
95
|
+
INSERT INTO rapport.companies (
|
|
96
|
+
company_id,
|
|
97
|
+
client_id,
|
|
98
|
+
company_name,
|
|
99
|
+
company_status
|
|
100
|
+
)
|
|
101
|
+
VALUES ($1, $2, $3, 'Active')
|
|
102
|
+
ON CONFLICT (company_id) DO NOTHING
|
|
103
|
+
RETURNING company_id
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
await executeQuery(companyQuery, [
|
|
107
|
+
companyId,
|
|
108
|
+
personalClientId,
|
|
109
|
+
'Personal Projects'
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
console.log('Personal company created:', companyId);
|
|
113
|
+
|
|
114
|
+
// 4. Create admin entitlement
|
|
115
|
+
const entitlementQuery = `
|
|
116
|
+
INSERT INTO rapport.user_entitlements (
|
|
117
|
+
email_address,
|
|
118
|
+
client_id,
|
|
119
|
+
company_id,
|
|
120
|
+
admin,
|
|
121
|
+
member
|
|
122
|
+
)
|
|
123
|
+
VALUES ($1, $2, $3, true, true)
|
|
124
|
+
ON CONFLICT (email_address, company_id) DO NOTHING
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
await executeQuery(entitlementQuery, [email, personalClientId, companyId]);
|
|
128
|
+
|
|
129
|
+
console.log('Entitlement created for:', email);
|
|
130
|
+
|
|
131
|
+
await executeQuery('COMMIT');
|
|
132
|
+
|
|
133
|
+
console.log('Post-confirmation complete for:', email);
|
|
134
|
+
|
|
135
|
+
} catch (error) {
|
|
136
|
+
await executeQuery('ROLLBACK');
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error('Post-confirmation error:', error);
|
|
142
|
+
// Don't throw - this would prevent user creation in Cognito
|
|
143
|
+
// Log the error and continue
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Must return the event for Cognito to proceed
|
|
147
|
+
return event;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = { handler };
|
|
@@ -0,0 +1,89 @@
|
|
|
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);
|
|
@@ -0,0 +1,114 @@
|
|
|
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
|
|
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
|
+
FROM rapport.users u
|
|
40
|
+
LEFT JOIN rapport.clients c ON u.client_id = c.client_id
|
|
41
|
+
WHERE u.email_address = $1
|
|
42
|
+
AND u.active = true
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
const result = await executeQuery(query, [email]);
|
|
46
|
+
|
|
47
|
+
if (result.rowCount === 0) {
|
|
48
|
+
return createErrorResponse(404, 'User not found');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const user = result.rows[0];
|
|
52
|
+
const tierConfig = getTierConfig(user.subscription_tier || 'free');
|
|
53
|
+
|
|
54
|
+
// Get usage counts
|
|
55
|
+
const usageQuery = `
|
|
56
|
+
SELECT
|
|
57
|
+
(SELECT COUNT(*) FROM rapport.user_entitlements WHERE client_id = $1) as collaborators,
|
|
58
|
+
(SELECT COUNT(*) FROM rapport.projects p
|
|
59
|
+
JOIN rapport.companies co ON p.company_id = co.company_id
|
|
60
|
+
WHERE co.client_id = $1 AND p.archived = false) as projects,
|
|
61
|
+
(SELECT COUNT(*) FROM rapport.invariants i
|
|
62
|
+
JOIN rapport.projects p ON i.project_id = p.project_id
|
|
63
|
+
JOIN rapport.companies co ON p.company_id = co.company_id
|
|
64
|
+
WHERE co.client_id = $1) as invariants
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
const usageResult = await executeQuery(usageQuery, [user.client_id]);
|
|
68
|
+
const usage = usageResult.rows[0];
|
|
69
|
+
|
|
70
|
+
return createSuccessResponse(
|
|
71
|
+
{
|
|
72
|
+
Records: [{
|
|
73
|
+
email_address: user.email_address,
|
|
74
|
+
first_name: user.first_name,
|
|
75
|
+
last_name: user.last_name,
|
|
76
|
+
client_id: user.client_id,
|
|
77
|
+
client_name: user.client_name,
|
|
78
|
+
user_status: user.user_status,
|
|
79
|
+
member_since: user.create_date,
|
|
80
|
+
subscription: {
|
|
81
|
+
tier: user.subscription_tier || 'free',
|
|
82
|
+
tier_name: tierConfig?.displayName || 'Free',
|
|
83
|
+
status: user.subscription_status || 'active',
|
|
84
|
+
ends_at: user.subscription_ends_at,
|
|
85
|
+
has_stripe: !!user.stripe_customer_id,
|
|
86
|
+
features: tierConfig?.features || []
|
|
87
|
+
},
|
|
88
|
+
usage: {
|
|
89
|
+
collaborators: parseInt(usage.collaborators) || 0,
|
|
90
|
+
projects: parseInt(usage.projects) || 0,
|
|
91
|
+
invariants: parseInt(usage.invariants) || 0
|
|
92
|
+
},
|
|
93
|
+
limits: {
|
|
94
|
+
max_collaborators: tierConfig?.maxCollaborators || 1,
|
|
95
|
+
max_projects: tierConfig?.maxProjects || 3,
|
|
96
|
+
max_invariants: tierConfig?.maxInvariants || 10
|
|
97
|
+
}
|
|
98
|
+
}]
|
|
99
|
+
},
|
|
100
|
+
'User profile retrieved',
|
|
101
|
+
{
|
|
102
|
+
Total_Records: 1,
|
|
103
|
+
Request_ID,
|
|
104
|
+
Timestamp: new Date().toISOString()
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error('Handler Error:', error);
|
|
110
|
+
return handleError(error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
exports.handler = wrapHandler(getUser);
|
|
@@ -0,0 +1,223 @@
|
|
|
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
|
|
14
|
+
const signature = headers['x-hub-signature-256'] || headers['X-Hub-Signature-256'];
|
|
15
|
+
const secret = process.env.GITHUB_WEBHOOK_SECRET;
|
|
16
|
+
|
|
17
|
+
if (!secret) {
|
|
18
|
+
console.error('GITHUB_WEBHOOK_SECRET not configured');
|
|
19
|
+
return createErrorResponse(500, 'Webhook not configured');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!verifySignature(JSON.stringify(body), signature, secret)) {
|
|
23
|
+
return createErrorResponse(401, 'Invalid signature');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const eventType = headers['x-github-event'] || headers['X-GitHub-Event'];
|
|
27
|
+
|
|
28
|
+
if (eventType === 'push') {
|
|
29
|
+
await handlePushEvent(body);
|
|
30
|
+
} else if (eventType === 'pull_request') {
|
|
31
|
+
await handlePREvent(body);
|
|
32
|
+
} else if (eventType === 'ping') {
|
|
33
|
+
// GitHub sends ping when webhook is first configured
|
|
34
|
+
return createSuccessResponse({ received: true }, 'Webhook configured');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return createSuccessResponse(
|
|
38
|
+
{ received: true, event: eventType },
|
|
39
|
+
'Webhook processed'
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
function verifySignature(payload, signature, secret) {
|
|
44
|
+
if (!signature) return false;
|
|
45
|
+
|
|
46
|
+
const expectedSignature = 'sha256=' + crypto
|
|
47
|
+
.createHmac('sha256', secret)
|
|
48
|
+
.update(payload)
|
|
49
|
+
.digest('hex');
|
|
50
|
+
|
|
51
|
+
return crypto.timingSafeEqual(
|
|
52
|
+
Buffer.from(signature),
|
|
53
|
+
Buffer.from(expectedSignature)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function handlePushEvent(payload) {
|
|
58
|
+
const repoFullName = payload.repository?.full_name;
|
|
59
|
+
if (!repoFullName) return;
|
|
60
|
+
|
|
61
|
+
// Find project by repo URL
|
|
62
|
+
const projectResult = await executeQuery(`
|
|
63
|
+
SELECT project_id FROM rapport.projects
|
|
64
|
+
WHERE repo_url LIKE $1
|
|
65
|
+
`, [`%${repoFullName}%`]);
|
|
66
|
+
|
|
67
|
+
if (projectResult.rowCount === 0) {
|
|
68
|
+
console.log(`Project not found for repo: ${repoFullName}`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const projectId = projectResult.rows[0].project_id;
|
|
73
|
+
const branch = payload.ref?.replace('refs/heads/', '') || 'unknown';
|
|
74
|
+
|
|
75
|
+
for (const commit of (payload.commits || [])) {
|
|
76
|
+
await recordCommit(commit, projectId, branch);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function recordCommit(commit, projectId, branch) {
|
|
81
|
+
const authorEmail = commit.author?.email;
|
|
82
|
+
if (!authorEmail) return;
|
|
83
|
+
|
|
84
|
+
// Find user by email
|
|
85
|
+
const userResult = await executeQuery(`
|
|
86
|
+
SELECT "Email_Address" FROM "Users"
|
|
87
|
+
WHERE "Email_Address" = $1 AND active = true
|
|
88
|
+
`, [authorEmail]);
|
|
89
|
+
|
|
90
|
+
if (userResult.rowCount === 0) {
|
|
91
|
+
console.log(`User not found for email: ${authorEmail}`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const email = userResult.rows[0].Email_Address;
|
|
96
|
+
|
|
97
|
+
// Find recent session for correlation
|
|
98
|
+
const sessionResult = await executeQuery(`
|
|
99
|
+
SELECT session_id, ended_at, started_at
|
|
100
|
+
FROM rapport.sessions
|
|
101
|
+
WHERE email_address = $1
|
|
102
|
+
AND project_id = $2
|
|
103
|
+
ORDER BY started_at DESC
|
|
104
|
+
LIMIT 1
|
|
105
|
+
`, [email, projectId]);
|
|
106
|
+
|
|
107
|
+
let sessionId = null;
|
|
108
|
+
let withinSession = false;
|
|
109
|
+
let timeSinceSession = null;
|
|
110
|
+
|
|
111
|
+
if (sessionResult.rowCount > 0) {
|
|
112
|
+
const session = sessionResult.rows[0];
|
|
113
|
+
const commitTime = new Date(commit.timestamp);
|
|
114
|
+
const sessionEnd = session.ended_at ? new Date(session.ended_at) : new Date();
|
|
115
|
+
const sessionStart = new Date(session.started_at);
|
|
116
|
+
|
|
117
|
+
// Commit within session if after start and before/during end
|
|
118
|
+
withinSession = commitTime >= sessionStart && (!session.ended_at || commitTime <= sessionEnd);
|
|
119
|
+
|
|
120
|
+
if (!withinSession && session.ended_at) {
|
|
121
|
+
timeSinceSession = Math.round((commitTime - sessionEnd) / (1000 * 60));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
sessionId = session.session_id;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Insert commit
|
|
128
|
+
await executeQuery(`
|
|
129
|
+
INSERT INTO rapport.commits (
|
|
130
|
+
commit_hash, project_id, email_address, branch, message,
|
|
131
|
+
committed_at, files_changed,
|
|
132
|
+
session_id, within_session, time_since_session_minutes
|
|
133
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
134
|
+
ON CONFLICT (commit_hash) DO NOTHING
|
|
135
|
+
`, [
|
|
136
|
+
commit.id,
|
|
137
|
+
projectId,
|
|
138
|
+
email,
|
|
139
|
+
branch,
|
|
140
|
+
commit.message?.substring(0, 500), // Truncate long messages
|
|
141
|
+
commit.timestamp,
|
|
142
|
+
(commit.added?.length || 0) + (commit.modified?.length || 0) + (commit.removed?.length || 0),
|
|
143
|
+
sessionId,
|
|
144
|
+
withinSession,
|
|
145
|
+
timeSinceSession
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
console.log(`Recorded commit ${commit.id} for ${email}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function handlePREvent(payload) {
|
|
152
|
+
const pr = payload.pull_request;
|
|
153
|
+
const repoFullName = payload.repository?.full_name;
|
|
154
|
+
if (!pr || !repoFullName) return;
|
|
155
|
+
|
|
156
|
+
// Find project by repo URL
|
|
157
|
+
const projectResult = await executeQuery(`
|
|
158
|
+
SELECT project_id FROM rapport.projects
|
|
159
|
+
WHERE repo_url LIKE $1
|
|
160
|
+
`, [`%${repoFullName}%`]);
|
|
161
|
+
|
|
162
|
+
if (projectResult.rowCount === 0) {
|
|
163
|
+
console.log(`Project not found for repo: ${repoFullName}`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const projectId = projectResult.rows[0].project_id;
|
|
168
|
+
const prId = `github:${repoFullName}:${pr.number}`;
|
|
169
|
+
|
|
170
|
+
// Try to find user by email or login
|
|
171
|
+
const authorEmail = pr.user?.email || `${pr.user?.login}@users.noreply.github.com`;
|
|
172
|
+
const userResult = await executeQuery(`
|
|
173
|
+
SELECT "Email_Address" FROM "Users"
|
|
174
|
+
WHERE "Email_Address" = $1 AND active = true
|
|
175
|
+
`, [authorEmail]);
|
|
176
|
+
|
|
177
|
+
if (userResult.rowCount === 0) {
|
|
178
|
+
console.log(`User not found for: ${authorEmail}`);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const email = userResult.rows[0].Email_Address;
|
|
183
|
+
|
|
184
|
+
// Calculate review time if merged
|
|
185
|
+
let reviewTimeHours = null;
|
|
186
|
+
if (pr.merged_at && pr.created_at) {
|
|
187
|
+
const created = new Date(pr.created_at);
|
|
188
|
+
const merged = new Date(pr.merged_at);
|
|
189
|
+
reviewTimeHours = (merged - created) / (1000 * 60 * 60);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Upsert PR
|
|
193
|
+
await executeQuery(`
|
|
194
|
+
INSERT INTO rapport.pull_requests (
|
|
195
|
+
pr_id, project_id, email_address, title, branch, base_branch,
|
|
196
|
+
status, opened_at, merged_at, closed_at,
|
|
197
|
+
commits_count, files_changed, additions, deletions, review_time_hours
|
|
198
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
|
|
199
|
+
ON CONFLICT (pr_id) DO UPDATE SET
|
|
200
|
+
status = EXCLUDED.status,
|
|
201
|
+
merged_at = EXCLUDED.merged_at,
|
|
202
|
+
closed_at = EXCLUDED.closed_at,
|
|
203
|
+
review_time_hours = EXCLUDED.review_time_hours
|
|
204
|
+
`, [
|
|
205
|
+
prId,
|
|
206
|
+
projectId,
|
|
207
|
+
email,
|
|
208
|
+
pr.title?.substring(0, 500),
|
|
209
|
+
pr.head?.ref,
|
|
210
|
+
pr.base?.ref,
|
|
211
|
+
pr.merged ? 'merged' : pr.state,
|
|
212
|
+
pr.created_at,
|
|
213
|
+
pr.merged_at,
|
|
214
|
+
pr.closed_at,
|
|
215
|
+
pr.commits,
|
|
216
|
+
pr.changed_files,
|
|
217
|
+
pr.additions,
|
|
218
|
+
pr.deletions,
|
|
219
|
+
reviewTimeHours
|
|
220
|
+
]);
|
|
221
|
+
|
|
222
|
+
console.log(`Recorded PR ${prId} for ${email}`);
|
|
223
|
+
}
|