@equilateral_ai/mindmeld 3.5.3 → 4.0.1
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 +20 -14
- package/scripts/init-project.js +9 -23
- 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/harvest.js +0 -601
- package/scripts/inject.js +0 -409
- package/scripts/mcp-bridge.js +0 -220
- package/scripts/repo-analyzer.js +0 -870
- 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,185 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pattern Violation Handler
|
|
3
|
-
* Records standards violations detected during Claude Code sessions
|
|
4
|
-
*
|
|
5
|
-
* POST /api/patterns/violations
|
|
6
|
-
* Body: { pattern, violations[], session_id, user_id, project_id, timestamp }
|
|
7
|
-
*
|
|
8
|
-
* Called by: pre-compact.js hook (recordViolation method)
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Record pattern violation
|
|
15
|
-
* Stores violation for compliance tracking and team learning
|
|
16
|
-
*/
|
|
17
|
-
async function recordPatternViolation({ body: requestBody = {}, requestContext }) {
|
|
18
|
-
try {
|
|
19
|
-
const Request_ID = requestContext?.requestId || 'unknown';
|
|
20
|
-
|
|
21
|
-
const {
|
|
22
|
-
pattern,
|
|
23
|
-
violations = [],
|
|
24
|
-
session_id,
|
|
25
|
-
user_id,
|
|
26
|
-
project_id,
|
|
27
|
-
timestamp
|
|
28
|
-
} = requestBody;
|
|
29
|
-
|
|
30
|
-
// Validate required fields
|
|
31
|
-
if (!pattern || !pattern.element) {
|
|
32
|
-
return createErrorResponse(400, 'pattern.element is required');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (!violations || violations.length === 0) {
|
|
36
|
-
return createErrorResponse(400, 'violations array is required');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (!session_id) {
|
|
40
|
-
return createErrorResponse(400, 'session_id is required');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Generate pattern_id from element if not provided
|
|
44
|
-
const patternId = pattern.pattern_id || `pat_${pattern.element.toLowerCase().replace(/\s+/g, '_').substring(0, 50)}`;
|
|
45
|
-
|
|
46
|
-
// If no project_id, we can't create a pattern record (FK constraint)
|
|
47
|
-
// Return success anyway - hooks should not fail
|
|
48
|
-
if (!project_id) {
|
|
49
|
-
console.log('[patternViolationPost] No project_id provided, skipping violation record');
|
|
50
|
-
return createSuccessResponse(
|
|
51
|
-
{
|
|
52
|
-
Records: [{
|
|
53
|
-
pattern_id: patternId,
|
|
54
|
-
violation_count: violations.length,
|
|
55
|
-
recorded: false,
|
|
56
|
-
reason: 'no_project_id',
|
|
57
|
-
message: 'Violation logged but not recorded (no project context)'
|
|
58
|
-
}]
|
|
59
|
-
},
|
|
60
|
-
'Violation acknowledged (no project context)',
|
|
61
|
-
{
|
|
62
|
-
Total_Records: 0,
|
|
63
|
-
Request_ID,
|
|
64
|
-
Timestamp: new Date().toISOString()
|
|
65
|
-
}
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Ensure the pattern exists (upsert with violation count)
|
|
70
|
-
const upsertPatternQuery = `
|
|
71
|
-
INSERT INTO rapport.patterns (
|
|
72
|
-
pattern_id,
|
|
73
|
-
project_id,
|
|
74
|
-
intent,
|
|
75
|
-
constraints,
|
|
76
|
-
outcome_criteria,
|
|
77
|
-
maturity,
|
|
78
|
-
handoff_count,
|
|
79
|
-
successful_handoffs,
|
|
80
|
-
failed_handoffs,
|
|
81
|
-
discovered_by,
|
|
82
|
-
discovered_at,
|
|
83
|
-
last_used,
|
|
84
|
-
pattern_data
|
|
85
|
-
) VALUES (
|
|
86
|
-
$1, $2, $3, $4, $5, 'provisional', 1, 0, 1, $6, NOW(), NOW(), $7
|
|
87
|
-
)
|
|
88
|
-
ON CONFLICT (pattern_id) DO UPDATE SET
|
|
89
|
-
handoff_count = rapport.patterns.handoff_count + 1,
|
|
90
|
-
failed_handoffs = rapport.patterns.failed_handoffs + 1,
|
|
91
|
-
last_used = NOW()
|
|
92
|
-
RETURNING pattern_id
|
|
93
|
-
`;
|
|
94
|
-
|
|
95
|
-
await executeQuery(upsertPatternQuery, [
|
|
96
|
-
patternId,
|
|
97
|
-
project_id || null,
|
|
98
|
-
pattern.intent || pattern.element,
|
|
99
|
-
JSON.stringify(pattern.constraints || []),
|
|
100
|
-
JSON.stringify(pattern.outcome_criteria || []),
|
|
101
|
-
user_id || 'anonymous',
|
|
102
|
-
JSON.stringify({
|
|
103
|
-
type: pattern.type,
|
|
104
|
-
category: pattern.category,
|
|
105
|
-
file: pattern.file
|
|
106
|
-
})
|
|
107
|
-
]);
|
|
108
|
-
|
|
109
|
-
// Record each violation
|
|
110
|
-
const violationRecords = [];
|
|
111
|
-
for (const violation of violations) {
|
|
112
|
-
const violationQuery = `
|
|
113
|
-
INSERT INTO rapport.pattern_usage (
|
|
114
|
-
pattern_id,
|
|
115
|
-
email_address,
|
|
116
|
-
session_id,
|
|
117
|
-
success,
|
|
118
|
-
context,
|
|
119
|
-
used_at
|
|
120
|
-
) VALUES (
|
|
121
|
-
$1, $2, $3, false, $4, $5
|
|
122
|
-
)
|
|
123
|
-
RETURNING usage_id
|
|
124
|
-
`;
|
|
125
|
-
|
|
126
|
-
const result = await executeQuery(violationQuery, [
|
|
127
|
-
patternId,
|
|
128
|
-
user_id || 'anonymous',
|
|
129
|
-
session_id,
|
|
130
|
-
JSON.stringify({
|
|
131
|
-
violation: true,
|
|
132
|
-
standard_id: violation.standard_id,
|
|
133
|
-
rule: violation.rule,
|
|
134
|
-
description: violation.description,
|
|
135
|
-
file: pattern.file,
|
|
136
|
-
category: pattern.category
|
|
137
|
-
}),
|
|
138
|
-
timestamp ? new Date(timestamp) : new Date()
|
|
139
|
-
]);
|
|
140
|
-
|
|
141
|
-
violationRecords.push({
|
|
142
|
-
usage_id: result.rows[0]?.usage_id,
|
|
143
|
-
standard_id: violation.standard_id,
|
|
144
|
-
rule: violation.rule
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Also record in session_standards if tracking standards shown
|
|
149
|
-
// Update to mark standard as violated
|
|
150
|
-
const updateStandardsQuery = `
|
|
151
|
-
UPDATE rapport.session_standards
|
|
152
|
-
SET violated = true
|
|
153
|
-
WHERE session_id = $1
|
|
154
|
-
AND pattern_id = ANY($2::varchar[])
|
|
155
|
-
`;
|
|
156
|
-
|
|
157
|
-
const standardIds = violations.map(v => v.standard_id).filter(Boolean);
|
|
158
|
-
if (standardIds.length > 0) {
|
|
159
|
-
await executeQuery(updateStandardsQuery, [session_id, standardIds]);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return createSuccessResponse(
|
|
163
|
-
{
|
|
164
|
-
Records: [{
|
|
165
|
-
pattern_id: patternId,
|
|
166
|
-
violation_count: violationRecords.length,
|
|
167
|
-
violations: violationRecords,
|
|
168
|
-
recorded: true
|
|
169
|
-
}]
|
|
170
|
-
},
|
|
171
|
-
`Recorded ${violationRecords.length} violation(s)`,
|
|
172
|
-
{
|
|
173
|
-
Total_Records: violationRecords.length,
|
|
174
|
-
Request_ID,
|
|
175
|
-
Timestamp: new Date().toISOString()
|
|
176
|
-
}
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
} catch (error) {
|
|
180
|
-
console.error('Handler Error:', error);
|
|
181
|
-
return handleError(error);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
exports.handler = wrapHandler(recordPatternViolation);
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Project Create Handler
|
|
3
|
-
* Creates new project for a company
|
|
4
|
-
*
|
|
5
|
-
* POST /api/projects
|
|
6
|
-
* Body: { Company_ID, project_name, description, private }
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError, checkSubscriptionLimits } = require('./helpers');
|
|
10
|
-
const crypto = require('crypto');
|
|
11
|
-
const https = require('https');
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Create project
|
|
15
|
-
* Requires Admin access to company
|
|
16
|
-
*/
|
|
17
|
-
async function createProject({ body: requestBody = {}, requestContext }) {
|
|
18
|
-
try {
|
|
19
|
-
const Request_ID = requestContext.requestId;
|
|
20
|
-
// REST API: requestContext.authorizer.claims.email
|
|
21
|
-
// HTTP API: requestContext.authorizer.jwt.claims.email
|
|
22
|
-
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
23
|
-
const { Company_ID, project_name, description, private: isPrivate, repo_url } = requestBody;
|
|
24
|
-
|
|
25
|
-
// Validate required fields
|
|
26
|
-
if (!Company_ID || !project_name) {
|
|
27
|
-
return createErrorResponse(400, 'Company_ID and project_name are required');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Check user has admin access to company
|
|
31
|
-
const adminQuery = `
|
|
32
|
-
SELECT ue.admin, c.company_name
|
|
33
|
-
FROM rapport.user_entitlements ue
|
|
34
|
-
JOIN rapport.companies c ON ue.company_id = c.company_id
|
|
35
|
-
WHERE ue.email_address = $1
|
|
36
|
-
AND ue.company_id = $2
|
|
37
|
-
`;
|
|
38
|
-
const adminCheck = await executeQuery(adminQuery, [email, Company_ID]);
|
|
39
|
-
|
|
40
|
-
if (adminCheck.rowCount === 0 || !adminCheck.rows[0].admin) {
|
|
41
|
-
return createErrorResponse(403, 'Admin access required to create projects');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Check project limit for subscription tier
|
|
45
|
-
const clientQuery = `
|
|
46
|
-
SELECT c.subscription_tier
|
|
47
|
-
FROM rapport.clients c
|
|
48
|
-
JOIN rapport.user_entitlements ue ON c.client_id = ue.client_id
|
|
49
|
-
WHERE ue.email_address = $1 AND ue.company_id = $2
|
|
50
|
-
`;
|
|
51
|
-
const clientResult = await executeQuery(clientQuery, [email, Company_ID]);
|
|
52
|
-
const subscriptionTier = clientResult.rows[0]?.subscription_tier || 'free';
|
|
53
|
-
|
|
54
|
-
const countQuery = `
|
|
55
|
-
SELECT COUNT(*) as project_count
|
|
56
|
-
FROM rapport.projects
|
|
57
|
-
WHERE company_id = $1
|
|
58
|
-
`;
|
|
59
|
-
const countResult = await executeQuery(countQuery, [Company_ID]);
|
|
60
|
-
const projectCount = parseInt(countResult.rows[0]?.project_count || '0');
|
|
61
|
-
|
|
62
|
-
const limitCheck = checkSubscriptionLimits(
|
|
63
|
-
{ subscription_tier: subscriptionTier },
|
|
64
|
-
'create_project',
|
|
65
|
-
{ projects: projectCount }
|
|
66
|
-
);
|
|
67
|
-
if (!limitCheck.allowed) {
|
|
68
|
-
return createErrorResponse(402, limitCheck.message);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Generate business-scoped project ID
|
|
72
|
-
// Format: prj_COMPANYID_timestamp
|
|
73
|
-
const timestamp = Date.now();
|
|
74
|
-
const project_id = `prj_${Company_ID}_${timestamp}`.toLowerCase().replace(/\s/g, '_');
|
|
75
|
-
|
|
76
|
-
// Create project
|
|
77
|
-
const query = `
|
|
78
|
-
INSERT INTO rapport.projects (
|
|
79
|
-
project_id,
|
|
80
|
-
company_id,
|
|
81
|
-
project_name,
|
|
82
|
-
description,
|
|
83
|
-
private,
|
|
84
|
-
repo_url
|
|
85
|
-
)
|
|
86
|
-
VALUES ($1, $2, $3, $4, $5, $6)
|
|
87
|
-
RETURNING
|
|
88
|
-
project_id,
|
|
89
|
-
company_id,
|
|
90
|
-
project_name,
|
|
91
|
-
description,
|
|
92
|
-
private,
|
|
93
|
-
repo_url,
|
|
94
|
-
created_at
|
|
95
|
-
`;
|
|
96
|
-
|
|
97
|
-
const result = await executeQuery(query, [
|
|
98
|
-
project_id,
|
|
99
|
-
Company_ID,
|
|
100
|
-
project_name,
|
|
101
|
-
description || null,
|
|
102
|
-
isPrivate || false,
|
|
103
|
-
repo_url || null
|
|
104
|
-
]);
|
|
105
|
-
|
|
106
|
-
// Add creator as owner
|
|
107
|
-
const collabQuery = `
|
|
108
|
-
INSERT INTO rapport.project_collaborators (
|
|
109
|
-
project_id,
|
|
110
|
-
email_address,
|
|
111
|
-
role
|
|
112
|
-
)
|
|
113
|
-
VALUES ($1, $2, 'owner')
|
|
114
|
-
`;
|
|
115
|
-
await executeQuery(collabQuery, [project_id, email]);
|
|
116
|
-
|
|
117
|
-
// Auto-create GitHub webhook if repo_url is a GitHub URL
|
|
118
|
-
let webhookCreated = false;
|
|
119
|
-
if (repo_url && repo_url.includes('github.com')) {
|
|
120
|
-
try {
|
|
121
|
-
webhookCreated = await createGitHubWebhook(email, repo_url, Company_ID);
|
|
122
|
-
} catch (webhookErr) {
|
|
123
|
-
console.warn('Webhook creation failed (non-blocking):', webhookErr.message);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return createSuccessResponse(
|
|
128
|
-
{ Records: result.rows, webhook_created: webhookCreated },
|
|
129
|
-
'Project created successfully',
|
|
130
|
-
{
|
|
131
|
-
Total_Records: result.rowCount,
|
|
132
|
-
Request_ID,
|
|
133
|
-
Timestamp: new Date().toISOString()
|
|
134
|
-
}
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
} catch (error) {
|
|
138
|
-
console.error('Handler Error:', error);
|
|
139
|
-
if (error.code === '23505') {
|
|
140
|
-
return createErrorResponse(409, 'Project already exists');
|
|
141
|
-
}
|
|
142
|
-
return handleError(error);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Create GitHub webhook on a repo when a project is connected
|
|
148
|
-
* Uses the customer's stored GitHub OAuth token and a per-repo secret
|
|
149
|
-
*/
|
|
150
|
-
async function createGitHubWebhook(email, repoUrl, companyId) {
|
|
151
|
-
// Extract owner/repo from URL
|
|
152
|
-
const match = repoUrl.match(/github\.com[/:]([^/]+)\/([^/.]+)/);
|
|
153
|
-
if (!match) {
|
|
154
|
-
console.log('Could not parse GitHub owner/repo from:', repoUrl);
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
const [, owner, repo] = match;
|
|
158
|
-
|
|
159
|
-
// Get the user's GitHub token
|
|
160
|
-
const connResult = await executeQuery(`
|
|
161
|
-
SELECT access_token_encrypted FROM rapport.github_connections
|
|
162
|
-
WHERE email_address = $1 AND revoked = FALSE
|
|
163
|
-
`, [email]);
|
|
164
|
-
|
|
165
|
-
if (connResult.rowCount === 0) {
|
|
166
|
-
console.log('No GitHub connection for', email);
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const encryptionKey = process.env.GITHUB_TOKEN_ENCRYPTION_KEY;
|
|
171
|
-
if (!encryptionKey) return false;
|
|
172
|
-
|
|
173
|
-
const accessToken = decryptToken(connResult.rows[0].access_token_encrypted, encryptionKey);
|
|
174
|
-
|
|
175
|
-
// Generate per-repo webhook secret
|
|
176
|
-
const webhookSecret = crypto.randomBytes(32).toString('hex');
|
|
177
|
-
const webhookUrl = process.env.WEBHOOK_URL || 'https://api.mindmeld.dev/api/webhooks/github';
|
|
178
|
-
|
|
179
|
-
// Create webhook via GitHub API
|
|
180
|
-
const payload = JSON.stringify({
|
|
181
|
-
name: 'web',
|
|
182
|
-
active: true,
|
|
183
|
-
events: ['push', 'pull_request'],
|
|
184
|
-
config: {
|
|
185
|
-
url: webhookUrl,
|
|
186
|
-
content_type: 'json',
|
|
187
|
-
secret: webhookSecret,
|
|
188
|
-
insecure_ssl: '0'
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
const response = await new Promise((resolve, reject) => {
|
|
193
|
-
const req = https.request({
|
|
194
|
-
hostname: 'api.github.com',
|
|
195
|
-
path: `/repos/${owner}/${repo}/hooks`,
|
|
196
|
-
method: 'POST',
|
|
197
|
-
headers: {
|
|
198
|
-
'Authorization': `Bearer ${accessToken}`,
|
|
199
|
-
'User-Agent': 'MindMeld-App',
|
|
200
|
-
'Accept': 'application/vnd.github+json',
|
|
201
|
-
'Content-Type': 'application/json',
|
|
202
|
-
'Content-Length': Buffer.byteLength(payload)
|
|
203
|
-
}
|
|
204
|
-
}, (res) => {
|
|
205
|
-
let data = '';
|
|
206
|
-
res.on('data', chunk => data += chunk);
|
|
207
|
-
res.on('end', () => {
|
|
208
|
-
try { resolve({ statusCode: res.statusCode, body: JSON.parse(data) }); }
|
|
209
|
-
catch (e) { resolve({ statusCode: res.statusCode, body: data }); }
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
req.on('error', reject);
|
|
213
|
-
req.write(payload);
|
|
214
|
-
req.end();
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
if (response.statusCode !== 201) {
|
|
218
|
-
console.warn(`GitHub webhook creation returned ${response.statusCode}:`, JSON.stringify(response.body).substring(0, 200));
|
|
219
|
-
return false;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const githubWebhookId = response.body.id;
|
|
223
|
-
|
|
224
|
-
// Register repo in git_repositories with per-repo webhook secret
|
|
225
|
-
// Unique constraint is on (company_id, repo_url)
|
|
226
|
-
await executeQuery(`
|
|
227
|
-
INSERT INTO rapport.git_repositories (
|
|
228
|
-
repo_id, repo_name, repo_url, company_id, webhook_secret, created_at, updated_at
|
|
229
|
-
) VALUES (gen_random_uuid(), $1, $2, $3, $4, NOW(), NOW())
|
|
230
|
-
ON CONFLICT (company_id, repo_url) DO UPDATE SET
|
|
231
|
-
webhook_secret = EXCLUDED.webhook_secret,
|
|
232
|
-
updated_at = NOW()
|
|
233
|
-
`, [`${owner}/${repo}`, repoUrl, companyId, webhookSecret]);
|
|
234
|
-
|
|
235
|
-
console.log(`Created GitHub webhook ${githubWebhookId} on ${owner}/${repo}`);
|
|
236
|
-
return true;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function decryptToken(encryptedData, key) {
|
|
240
|
-
const [ivHex, encrypted] = encryptedData.split(':');
|
|
241
|
-
const iv = Buffer.from(ivHex, 'hex');
|
|
242
|
-
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key, 'hex'), iv);
|
|
243
|
-
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
244
|
-
decrypted += decipher.final('utf8');
|
|
245
|
-
return decrypted;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
exports.handler = wrapHandler(createProject);
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Project Delete Handler
|
|
3
|
-
* Archives (soft deletes) a project
|
|
4
|
-
*
|
|
5
|
-
* DELETE /api/projects?project_id=xxx
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Archive project (soft delete)
|
|
12
|
-
* Requires owner role or company admin
|
|
13
|
-
*/
|
|
14
|
-
async function deleteProject({ queryStringParameters: queryParams = {}, 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
|
-
const projectId = queryParams.project_id;
|
|
21
|
-
|
|
22
|
-
if (!projectId) {
|
|
23
|
-
return createErrorResponse(400, 'projectId is required');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Check user has access to project
|
|
27
|
-
const accessQuery = `
|
|
28
|
-
SELECT
|
|
29
|
-
p.project_id,
|
|
30
|
-
p.company_id,
|
|
31
|
-
pc.role,
|
|
32
|
-
ue.admin as company_admin
|
|
33
|
-
FROM rapport.projects p
|
|
34
|
-
LEFT JOIN rapport.project_collaborators pc
|
|
35
|
-
ON p.project_id = pc.project_id
|
|
36
|
-
AND pc.email_address = $1
|
|
37
|
-
LEFT JOIN rapport.user_entitlements ue
|
|
38
|
-
ON ue.email_address = $1
|
|
39
|
-
AND ue.company_id = p.company_id
|
|
40
|
-
WHERE p.project_id = $2
|
|
41
|
-
`;
|
|
42
|
-
const accessCheck = await executeQuery(accessQuery, [email, projectId]);
|
|
43
|
-
|
|
44
|
-
if (accessCheck.rowCount === 0) {
|
|
45
|
-
return createErrorResponse(404, 'Project not found');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const access = accessCheck.rows[0];
|
|
49
|
-
|
|
50
|
-
// Check permissions (only owner or company admin can delete)
|
|
51
|
-
const canDelete = access.role === 'owner' || access.company_admin === true;
|
|
52
|
-
if (!canDelete) {
|
|
53
|
-
return createErrorResponse(403, 'Only project owner or company admin can delete project');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Archive project (soft delete)
|
|
57
|
-
const query = `
|
|
58
|
-
UPDATE rapport.projects
|
|
59
|
-
SET archived = true, updated_at = NOW()
|
|
60
|
-
WHERE project_id = $1
|
|
61
|
-
RETURNING project_id, project_name, archived
|
|
62
|
-
`;
|
|
63
|
-
|
|
64
|
-
const result = await executeQuery(query, [projectId]);
|
|
65
|
-
|
|
66
|
-
return createSuccessResponse(
|
|
67
|
-
{ Records: result.rows },
|
|
68
|
-
'Project archived successfully',
|
|
69
|
-
{
|
|
70
|
-
Total_Records: result.rowCount,
|
|
71
|
-
Request_ID,
|
|
72
|
-
Timestamp: new Date().toISOString()
|
|
73
|
-
}
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
} catch (error) {
|
|
77
|
-
console.error('Handler Error:', error);
|
|
78
|
-
return handleError(error);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
exports.handler = wrapHandler(deleteProject);
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Project Get Handler
|
|
3
|
-
* Retrieves projects for a company with collaborator access check
|
|
4
|
-
*
|
|
5
|
-
* GET /api/projects?Company_ID=xxx
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Get projects
|
|
12
|
-
* Returns all projects user has access to (via company entitlements or direct collaboration)
|
|
13
|
-
*/
|
|
14
|
-
async function getProjects({ queryStringParameters: queryParams = {}, 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
|
-
// Get user's company access
|
|
22
|
-
const entitlementQuery = `
|
|
23
|
-
SELECT ue.company_id, ue.admin, c.company_name
|
|
24
|
-
FROM rapport.user_entitlements ue
|
|
25
|
-
JOIN rapport.companies c ON ue.company_id = c.company_id
|
|
26
|
-
WHERE ue.email_address = $1
|
|
27
|
-
`;
|
|
28
|
-
const entitlements = await executeQuery(entitlementQuery, [email]);
|
|
29
|
-
|
|
30
|
-
if (entitlements.rowCount === 0) {
|
|
31
|
-
return createErrorResponse(403, 'No company access');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const companyIds = entitlements.rows.map(e => e.company_id);
|
|
35
|
-
|
|
36
|
-
// Optional filter by specific company
|
|
37
|
-
let targetCompanyIds = companyIds;
|
|
38
|
-
if (queryParams.Company_ID) {
|
|
39
|
-
if (!companyIds.includes(queryParams.Company_ID)) {
|
|
40
|
-
return createErrorResponse(403, 'No access to specified company');
|
|
41
|
-
}
|
|
42
|
-
targetCompanyIds = [queryParams.Company_ID];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Get projects for user's companies
|
|
46
|
-
const query = `
|
|
47
|
-
SELECT
|
|
48
|
-
p.project_id,
|
|
49
|
-
p.company_id,
|
|
50
|
-
p.project_name,
|
|
51
|
-
p.description,
|
|
52
|
-
p.private,
|
|
53
|
-
p.created_at,
|
|
54
|
-
p.last_active,
|
|
55
|
-
p.archived,
|
|
56
|
-
c.company_name,
|
|
57
|
-
COUNT(DISTINCT pc.email_address) as collaborator_count,
|
|
58
|
-
COALESCE(
|
|
59
|
-
json_agg(
|
|
60
|
-
json_build_object(
|
|
61
|
-
'email', pc.email_address,
|
|
62
|
-
'role', pc.role,
|
|
63
|
-
'is_external', pc.is_external
|
|
64
|
-
)
|
|
65
|
-
) FILTER (WHERE pc.email_address IS NOT NULL),
|
|
66
|
-
'[]'
|
|
67
|
-
) as collaborators
|
|
68
|
-
FROM rapport.projects p
|
|
69
|
-
JOIN rapport.companies c ON p.company_id = c.company_id
|
|
70
|
-
LEFT JOIN rapport.project_collaborators pc ON p.project_id = pc.project_id
|
|
71
|
-
WHERE p.company_id = ANY($1::varchar[])
|
|
72
|
-
AND p.archived = false
|
|
73
|
-
GROUP BY p.project_id, c.company_name
|
|
74
|
-
ORDER BY p.last_active DESC NULLS LAST, p.created_at DESC
|
|
75
|
-
`;
|
|
76
|
-
|
|
77
|
-
const result = await executeQuery(query, [targetCompanyIds]);
|
|
78
|
-
|
|
79
|
-
return createSuccessResponse(
|
|
80
|
-
{ Records: result.rows },
|
|
81
|
-
'Projects retrieved successfully',
|
|
82
|
-
{
|
|
83
|
-
Total_Records: result.rowCount,
|
|
84
|
-
Request_ID,
|
|
85
|
-
Timestamp: new Date().toISOString()
|
|
86
|
-
}
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
} catch (error) {
|
|
90
|
-
console.error('Handler Error:', error);
|
|
91
|
-
return handleError(error);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
exports.handler = wrapHandler(getProjects);
|