@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.
Files changed (138) hide show
  1. package/hooks/session-start.js +312 -85
  2. package/package.json +21 -13
  3. package/scripts/init-project.js +9 -23
  4. package/scripts/repo-analyzer.js +118 -2
  5. package/src/client/dbShim.js +16 -0
  6. package/src/core/AuthManager.js +3 -2
  7. package/src/handlers/helpers/dbOperations.js +9 -46
  8. package/src/index.js +2 -217
  9. package/src/utils/piiMask.js +16 -0
  10. package/scripts/inject.js +0 -409
  11. package/scripts/mcp-bridge.js +0 -220
  12. package/scripts/standards.js +0 -285
  13. package/src/collaboration/CollaborationPrompt.js +0 -460
  14. package/src/core/AlertEngine.js +0 -813
  15. package/src/core/AlertNotifier.js +0 -363
  16. package/src/core/CorrelationAnalyzer.js +0 -931
  17. package/src/core/CrossReferenceEngine.js +0 -624
  18. package/src/core/CurationEngine.js +0 -688
  19. package/src/core/DeprecationScheduler.js +0 -183
  20. package/src/core/LoadBearingDetector.js +0 -242
  21. package/src/core/NotificationService.js +0 -1032
  22. package/src/core/RapportOrchestrator.js +0 -632
  23. package/src/core/RelevanceDetector.js +0 -694
  24. package/src/core/StandardLifecycle.js +0 -244
  25. package/src/core/StandardsIngestion.js +0 -991
  26. package/src/core/TeamLoadBearingDetector.js +0 -431
  27. package/src/core/parsers/adrParser.js +0 -479
  28. package/src/core/parsers/cursorRulesParser.js +0 -564
  29. package/src/core/parsers/eslintParser.js +0 -439
  30. package/src/database/dbOperations.js +0 -105
  31. package/src/handlers/activity/activityGetMe.js +0 -98
  32. package/src/handlers/activity/activityGetTeam.js +0 -175
  33. package/src/handlers/admin/adminSetup.js +0 -216
  34. package/src/handlers/alerts/alertsAcknowledge.js +0 -92
  35. package/src/handlers/alerts/alertsGet.js +0 -250
  36. package/src/handlers/analytics/activitySummaryGet.js +0 -234
  37. package/src/handlers/analytics/coachingGet.js +0 -361
  38. package/src/handlers/analytics/convergenceGet.js +0 -236
  39. package/src/handlers/analytics/developerScoreGet.js +0 -137
  40. package/src/handlers/collaborators/collaboratorAdd.js +0 -200
  41. package/src/handlers/collaborators/collaboratorInvite.js +0 -219
  42. package/src/handlers/collaborators/collaboratorList.js +0 -82
  43. package/src/handlers/collaborators/collaboratorRemove.js +0 -128
  44. package/src/handlers/collaborators/inviteAccept.js +0 -122
  45. package/src/handlers/company/companyUsersDelete.js +0 -141
  46. package/src/handlers/company/companyUsersGet.js +0 -90
  47. package/src/handlers/company/companyUsersPost.js +0 -267
  48. package/src/handlers/company/companyUsersPut.js +0 -76
  49. package/src/handlers/context/contextGet.js +0 -57
  50. package/src/handlers/context/invariantsGet.js +0 -74
  51. package/src/handlers/context/loopsGet.js +0 -82
  52. package/src/handlers/context/notesCreate.js +0 -74
  53. package/src/handlers/context/purposeGet.js +0 -78
  54. package/src/handlers/correlations/correlationsDeveloperGet.js +0 -227
  55. package/src/handlers/correlations/correlationsGet.js +0 -93
  56. package/src/handlers/correlations/correlationsProjectGet.js +0 -153
  57. package/src/handlers/enterprise/controlTowerGet.js +0 -224
  58. package/src/handlers/enterprise/enterpriseAuditGet.js +0 -108
  59. package/src/handlers/enterprise/enterpriseContributorsGet.js +0 -85
  60. package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +0 -53
  61. package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +0 -77
  62. package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +0 -71
  63. package/src/handlers/enterprise/enterpriseKnowledgeGet.js +0 -87
  64. package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +0 -122
  65. package/src/handlers/enterprise/enterpriseOnboardingComplete.js +0 -77
  66. package/src/handlers/enterprise/enterpriseOnboardingInvite.js +0 -138
  67. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +0 -128
  68. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +0 -88
  69. package/src/handlers/github/githubConnectionStatus.js +0 -49
  70. package/src/handlers/github/githubDiscoverPatterns.js +0 -621
  71. package/src/handlers/github/githubOAuthCallback.js +0 -178
  72. package/src/handlers/github/githubOAuthStart.js +0 -59
  73. package/src/handlers/github/githubPatternsReview.js +0 -76
  74. package/src/handlers/github/githubReposList.js +0 -105
  75. package/src/handlers/health/healthGet.js +0 -55
  76. package/src/handlers/helpers/auditLogger.js +0 -201
  77. package/src/handlers/helpers/checkSuperAdmin.js +0 -84
  78. package/src/handlers/helpers/decisionFrames.js +0 -29
  79. package/src/handlers/helpers/errorHandler.js +0 -49
  80. package/src/handlers/helpers/index.js +0 -138
  81. package/src/handlers/helpers/lambdaWrapper.js +0 -60
  82. package/src/handlers/helpers/mindmeldMcpCore.js +0 -1103
  83. package/src/handlers/helpers/predictiveCache.js +0 -51
  84. package/src/handlers/helpers/projectAccess.js +0 -88
  85. package/src/handlers/helpers/responseUtil.js +0 -55
  86. package/src/handlers/helpers/subscriptionTiers.js +0 -1168
  87. package/src/handlers/mcp/mcpHandler.js +0 -569
  88. package/src/handlers/mcp/mindmeldMcpHandler.js +0 -124
  89. package/src/handlers/mcp/mindmeldMcpStreamHandler.js +0 -342
  90. package/src/handlers/notifications/getPreferences.js +0 -84
  91. package/src/handlers/notifications/sendNotification.js +0 -170
  92. package/src/handlers/notifications/updatePreferences.js +0 -316
  93. package/src/handlers/patterns/patternEvaluatePromotionPost.js +0 -173
  94. package/src/handlers/patterns/patternUsagePost.js +0 -182
  95. package/src/handlers/patterns/patternViolationPost.js +0 -185
  96. package/src/handlers/projects/projectCreate.js +0 -248
  97. package/src/handlers/projects/projectDelete.js +0 -82
  98. package/src/handlers/projects/projectGet.js +0 -95
  99. package/src/handlers/projects/projectUpdate.js +0 -117
  100. package/src/handlers/reports/aiLeverage.js +0 -210
  101. package/src/handlers/reports/engineeringInvestment.js +0 -132
  102. package/src/handlers/reports/riskForecast.js +0 -206
  103. package/src/handlers/reports/standardsRoi.js +0 -254
  104. package/src/handlers/scheduled/analyzeCorrelations.js +0 -178
  105. package/src/handlers/scheduled/analyzeGitHistory.js +0 -510
  106. package/src/handlers/scheduled/generateAlerts.js +0 -135
  107. package/src/handlers/scheduled/maturityUpdateJob.js +0 -166
  108. package/src/handlers/scheduled/refreshActivity.js +0 -21
  109. package/src/handlers/scheduled/scanCompliance.js +0 -334
  110. package/src/handlers/sessions/sessionEndPost.js +0 -180
  111. package/src/handlers/sessions/sessionStandardsPost.js +0 -171
  112. package/src/handlers/standards/catalogGet.js +0 -185
  113. package/src/handlers/standards/catalogSync.js +0 -120
  114. package/src/handlers/standards/discoveriesGet.js +0 -89
  115. package/src/handlers/standards/projectStandardsGet.js +0 -129
  116. package/src/handlers/standards/projectStandardsPut.js +0 -151
  117. package/src/handlers/standards/standardsAuditGet.js +0 -65
  118. package/src/handlers/standards/standardsParseUpload.js +0 -149
  119. package/src/handlers/standards/standardsRelevantPost.js +0 -405
  120. package/src/handlers/standards/standardsTransition.js +0 -161
  121. package/src/handlers/stripe/addonManagePost.js +0 -240
  122. package/src/handlers/stripe/billingPortalPost.js +0 -93
  123. package/src/handlers/stripe/enterpriseCheckoutPost.js +0 -272
  124. package/src/handlers/stripe/seatsUpdatePost.js +0 -185
  125. package/src/handlers/stripe/subscriptionCancelDelete.js +0 -169
  126. package/src/handlers/stripe/subscriptionCreatePost.js +0 -221
  127. package/src/handlers/stripe/subscriptionUpdatePut.js +0 -163
  128. package/src/handlers/stripe/webhookPost.js +0 -482
  129. package/src/handlers/user/apiTokenCreate.js +0 -71
  130. package/src/handlers/user/apiTokenList.js +0 -64
  131. package/src/handlers/user/userSplashAck.js +0 -91
  132. package/src/handlers/user/userSplashGet.js +0 -211
  133. package/src/handlers/users/cognitoPostConfirmation.js +0 -186
  134. package/src/handlers/users/cognitoPreSignUp.js +0 -114
  135. package/src/handlers/users/userEntitlementsGet.js +0 -89
  136. package/src/handlers/users/userGet.js +0 -118
  137. package/src/handlers/users/userProfilePut.js +0 -77
  138. package/src/handlers/webhooks/githubWebhook.js +0 -215
@@ -1,137 +0,0 @@
1
- /**
2
- * Developer Score Analytics Handler
3
- * Quality scoring and standards adherence metrics per developer
4
- *
5
- * GET /api/analytics/developer-scores
6
- * Query params:
7
- * - project_id (required) - Project to scope metrics
8
- * - period (optional, default: 30d) - 30d or 90d
9
- *
10
- * Auth: Cognito JWT required, Manager or Admin role
11
- */
12
-
13
- const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
14
-
15
- async function getDeveloperScores({ requestContext, queryStringParameters }) {
16
- const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
17
-
18
- if (!email) {
19
- return createErrorResponse(401, 'Unauthorized');
20
- }
21
-
22
- const params = queryStringParameters || {};
23
- const projectId = params.project_id;
24
- const period = params.period || '30d';
25
-
26
- if (!projectId) {
27
- return createErrorResponse(400, 'project_id is required');
28
- }
29
-
30
- if (!['30d', '90d'].includes(period)) {
31
- return createErrorResponse(400, 'period must be 30d or 90d');
32
- }
33
-
34
- // Validate access - must be manager/admin with access to this project
35
- const accessCheck = await executeQuery(`
36
- SELECT ue.company_id
37
- FROM rapport.user_entitlements ue
38
- JOIN rapport.projects p ON p.company_id = ue.company_id
39
- WHERE ue.email_address = $1
40
- AND p.project_id = $2
41
- AND (ue.admin = true OR ue.manager = true)
42
- LIMIT 1
43
- `, [email, projectId]);
44
-
45
- if (accessCheck.rows.length === 0) {
46
- return createErrorResponse(403, 'Manager or Admin access required for this project');
47
- }
48
-
49
- const companyId = accessCheck.rows[0].company_id;
50
- const periodDays = period === '90d' ? 90 : 30;
51
- const periodStart = new Date();
52
- periodStart.setDate(periodStart.getDate() - periodDays);
53
-
54
- // Fetch developer metrics — join pattern_usage through sessions for project scope
55
- const developerResult = await executeQuery(`
56
- SELECT
57
- u.email_address as user_email,
58
- CONCAT(u.first_name, ' ', u.last_name) as display_name,
59
- COALESCE(pu_counts.patterns_used, 0) as patterns_used,
60
- COALESCE(dm_scores.compliance_score, 0) as compliance_score,
61
- MAX(sc.session_started_at) as last_active
62
- FROM rapport.user_entitlements ue
63
- JOIN rapport.users u ON u.email_address = ue.email_address
64
- LEFT JOIN (
65
- SELECT pu.email_address, COUNT(*) as patterns_used
66
- FROM rapport.pattern_usage pu
67
- JOIN rapport.sessions s ON pu.session_id = s.session_id
68
- WHERE s.project_id = $2
69
- AND pu.used_at >= $3
70
- GROUP BY pu.email_address
71
- ) pu_counts ON pu_counts.email_address = u.email_address
72
- LEFT JOIN (
73
- SELECT dm.developer_email, AVG(dm.compliance_score) as compliance_score
74
- FROM rapport.developer_metrics dm
75
- JOIN rapport.git_repositories r ON dm.repo_id = r.repo_id
76
- JOIN rapport.projects p ON p.repo_url = r.repo_url
77
- WHERE p.project_id = $2
78
- AND dm.period_start >= $3
79
- GROUP BY dm.developer_email
80
- ) dm_scores ON dm_scores.developer_email = u.email_address
81
- LEFT JOIN rapport.session_correlations sc ON sc.email_address = u.email_address
82
- AND sc.project_id = $2
83
- AND sc.session_started_at >= $3
84
- WHERE ue.company_id = $1
85
- GROUP BY u.email_address, u.first_name, u.last_name,
86
- pu_counts.patterns_used, dm_scores.compliance_score
87
- ORDER BY u.first_name ASC, u.last_name ASC
88
- `, [companyId, projectId, periodStart]);
89
-
90
- // Build developer list with quality scores
91
- const developers = developerResult.rows.map(row => {
92
- const patternsUsed = parseInt(row.patterns_used) || 0;
93
- const complianceScore = parseFloat(row.compliance_score) || 0;
94
- const standardsAdherence = complianceScore / 100;
95
-
96
- // Quality score: patterns used + standards adherence, capped 0-100
97
- const rawScore = (patternsUsed * 2) + (standardsAdherence * 50);
98
- const qualityScore = Math.max(0, Math.min(100, Math.round(rawScore)));
99
-
100
- return {
101
- user_email: row.user_email,
102
- display_name: row.display_name || row.user_email.split('@')[0],
103
- quality_score: qualityScore,
104
- standards_adherence: parseFloat(standardsAdherence.toFixed(2)),
105
- patterns_used: patternsUsed,
106
- violations: 0,
107
- violations_by_category: {},
108
- trend: 'stable',
109
- last_active: row.last_active
110
- ? new Date(row.last_active).toISOString().split('T')[0]
111
- : null
112
- };
113
- });
114
-
115
- // Calculate team summary
116
- const totalDevelopers = developers.length;
117
- const avgQualityScore = totalDevelopers > 0
118
- ? Math.round(developers.reduce((sum, d) => sum + d.quality_score, 0) / totalDevelopers)
119
- : 0;
120
- const avgAdherence = totalDevelopers > 0
121
- ? parseFloat((developers.reduce((sum, d) => sum + d.standards_adherence, 0) / totalDevelopers).toFixed(2))
122
- : 0;
123
-
124
- return createSuccessResponse({
125
- team_summary: {
126
- total_developers: totalDevelopers,
127
- avg_quality_score: avgQualityScore,
128
- standards_adherence: avgAdherence,
129
- total_violations: 0,
130
- period
131
- },
132
- developers,
133
- trend_data: []
134
- }, 'Developer scores retrieved successfully');
135
- }
136
-
137
- exports.handler = wrapHandler(getDeveloperScores);
@@ -1,200 +0,0 @@
1
- /**
2
- * Collaborator Add Handler
3
- * Adds a collaborator to a project
4
- *
5
- * POST /api/collaborators
6
- * Body: { project_id, email, role }
7
- * Auth: Cognito JWT required
8
- */
9
-
10
- const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError, checkCollaboratorBillingLimits, checkEnterpriseSeatLimits } = require('./helpers');
11
-
12
- /**
13
- * Add collaborator to project
14
- * Requires owner or admin role on project
15
- */
16
- async function addCollaborator({ body: requestBody = {}, requestContext }) {
17
- try {
18
- const Request_ID = requestContext.requestId;
19
- const inviterEmail = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
20
- const { project_id: projectId, email, role = 'collaborator' } = requestBody;
21
-
22
- if (!inviterEmail) {
23
- return createErrorResponse(401, 'Authentication required');
24
- }
25
-
26
- if (!projectId) {
27
- return createErrorResponse(400, 'projectId is required');
28
- }
29
-
30
- if (!email) {
31
- return createErrorResponse(400, 'email is required');
32
- }
33
-
34
- // Validate role
35
- const validRoles = ['admin', 'collaborator', 'viewer'];
36
- if (!validRoles.includes(role)) {
37
- return createErrorResponse(400, `Invalid role. Must be one of: ${validRoles.join(', ')}`);
38
- }
39
-
40
- // Check inviter has permission (owner or admin)
41
- const accessQuery = `
42
- SELECT
43
- pc.role,
44
- p.company_id,
45
- c.client_id,
46
- c.subscription_tier,
47
- c.billing_type,
48
- c.billable_users,
49
- c.seat_count,
50
- c.enterprise_package
51
- FROM rapport.projects p
52
- JOIN rapport.project_collaborators pc
53
- ON p.project_id = pc.project_id
54
- AND pc.email_address = $1
55
- JOIN rapport.clients c ON p.company_id = c.client_id
56
- WHERE p.project_id = $2
57
- `;
58
- const accessCheck = await executeQuery(accessQuery, [inviterEmail, projectId]);
59
-
60
- if (accessCheck.rowCount === 0) {
61
- return createErrorResponse(403, 'You do not have access to this project');
62
- }
63
-
64
- const access = accessCheck.rows[0];
65
- if (access.role !== 'owner' && access.role !== 'admin') {
66
- return createErrorResponse(403, 'Only project owners and admins can add collaborators');
67
- }
68
-
69
- // Check billing/subscription limits based on billing type
70
- const collaboratorCountQuery = `
71
- SELECT COUNT(*) as count
72
- FROM rapport.project_collaborators
73
- WHERE project_id = $1
74
- `;
75
- const countResult = await executeQuery(collaboratorCountQuery, [projectId]);
76
- const currentCount = parseInt(countResult.rows[0].count) || 0;
77
-
78
- // For enterprise tier, check seat limits
79
- if (access.subscription_tier === 'enterprise') {
80
- // Count total users across all projects for this client
81
- const totalUsersQuery = `
82
- SELECT COUNT(DISTINCT pc.email_address) as total_users
83
- FROM rapport.project_collaborators pc
84
- JOIN rapport.projects p ON pc.project_id = p.project_id
85
- WHERE p.company_id = $1
86
- `;
87
- const totalUsersResult = await executeQuery(totalUsersQuery, [access.client_id]);
88
- const totalUsers = parseInt(totalUsersResult.rows[0].total_users) || 0;
89
-
90
- const seatCheck = checkEnterpriseSeatLimits(
91
- {
92
- subscription_tier: access.subscription_tier,
93
- seat_count: access.seat_count
94
- },
95
- totalUsers
96
- );
97
-
98
- if (!seatCheck.allowed) {
99
- return createErrorResponse(403, seatCheck.message, {
100
- code: 'ENTERPRISE_SEAT_LIMIT',
101
- seatsUsed: seatCheck.seatsUsed,
102
- seatCount: seatCheck.seatCount,
103
- billingAction: seatCheck.billingAction
104
- });
105
- }
106
- }
107
-
108
- const billingCheck = checkCollaboratorBillingLimits(
109
- {
110
- subscription_tier: access.subscription_tier,
111
- billing_type: access.billing_type
112
- },
113
- currentCount
114
- );
115
-
116
- if (!billingCheck.allowed) {
117
- return createErrorResponse(403, billingCheck.message, {
118
- code: 'SUBSCRIPTION_LIMIT',
119
- current: currentCount,
120
- limit: billingCheck.limit
121
- });
122
- }
123
-
124
- // Check if collaborator already exists
125
- const existsQuery = `
126
- SELECT email_address FROM rapport.project_collaborators
127
- WHERE project_id = $1 AND email_address = $2
128
- `;
129
- const existsCheck = await executeQuery(existsQuery, [projectId, email]);
130
-
131
- if (existsCheck.rowCount > 0) {
132
- return createErrorResponse(409, 'User is already a collaborator on this project');
133
- }
134
-
135
- // Check if user exists in system (internal) or is external
136
- const userQuery = `SELECT email_address FROM rapport.users WHERE email_address = $1`;
137
- const userCheck = await executeQuery(userQuery, [email]);
138
- const isExternal = userCheck.rowCount === 0;
139
-
140
- // Add collaborator
141
- const insertQuery = `
142
- INSERT INTO rapport.project_collaborators
143
- (project_id, email_address, role, invited_by, invited_at, is_external, accepted_at)
144
- VALUES
145
- ($1, $2, $3, $4, NOW(), $5, ${isExternal ? 'NULL' : 'NOW()'})
146
- RETURNING
147
- project_id,
148
- email_address,
149
- role,
150
- invited_by,
151
- invited_at,
152
- is_external,
153
- accepted_at
154
- `;
155
-
156
- const result = await executeQuery(insertQuery, [
157
- projectId,
158
- email,
159
- role,
160
- inviterEmail,
161
- isExternal
162
- ]);
163
-
164
- const collaborator = result.rows[0];
165
-
166
- // For enterprise invoice billing, increment billable_users count
167
- if (billingCheck.billingAction === 'increment_billable_users') {
168
- await executeQuery(`
169
- UPDATE rapport.clients
170
- SET billable_users = COALESCE(billable_users, 0) + 1,
171
- last_updated = NOW()
172
- WHERE client_id = $1
173
- `, [access.client_id]);
174
- }
175
-
176
- return createSuccessResponse(
177
- {
178
- Records: [{
179
- ...collaborator,
180
- status: isExternal ? 'pending_invite' : 'active',
181
- message: isExternal
182
- ? 'Collaborator added. Send invite to complete setup.'
183
- : 'Collaborator added successfully.'
184
- }]
185
- },
186
- isExternal ? 'Collaborator added (pending invite)' : 'Collaborator added',
187
- {
188
- Total_Records: 1,
189
- Request_ID,
190
- Timestamp: new Date().toISOString()
191
- }
192
- );
193
-
194
- } catch (error) {
195
- console.error('Handler Error:', error);
196
- return handleError(error);
197
- }
198
- }
199
-
200
- exports.handler = wrapHandler(addCollaborator);
@@ -1,219 +0,0 @@
1
- /**
2
- * Collaborator Invite Handler
3
- * Sends invitation email to a collaborator
4
- *
5
- * POST /api/collaborators/invite
6
- * Body: { project_id, email }
7
- * Auth: Cognito JWT required
8
- */
9
-
10
- const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
11
- const { SESClient, SendEmailCommand } = require('@aws-sdk/client-ses');
12
-
13
- const ses = new SESClient({ region: process.env.AWS_REGION || 'us-east-2' });
14
-
15
- /**
16
- * Send invitation email to collaborator
17
- * Requires owner or admin role on project
18
- */
19
- async function inviteCollaborator({ body: requestBody = {}, requestContext }) {
20
- try {
21
- const Request_ID = requestContext.requestId;
22
- const inviterEmail = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
23
- const { project_id: projectId, email: collaboratorEmail } = requestBody;
24
-
25
- if (!inviterEmail) {
26
- return createErrorResponse(401, 'Authentication required');
27
- }
28
-
29
- if (!projectId || !collaboratorEmail) {
30
- return createErrorResponse(400, 'project_id and email are required');
31
- }
32
-
33
- const targetEmail = collaboratorEmail;
34
-
35
- // Check inviter has permission and get project details
36
- const accessQuery = `
37
- SELECT
38
- pc.role as inviter_role,
39
- p.project_name,
40
- p.description,
41
- tc.email_address as target_email,
42
- tc.role as target_role,
43
- tc.invited_at,
44
- tc.accepted_at,
45
- u.full_name as inviter_name
46
- FROM rapport.projects p
47
- JOIN rapport.project_collaborators pc
48
- ON p.project_id = pc.project_id
49
- AND pc.email_address = $1
50
- JOIN rapport.project_collaborators tc
51
- ON p.project_id = tc.project_id
52
- AND tc.email_address = $3
53
- LEFT JOIN rapport.users u ON u.email_address = $1
54
- WHERE p.project_id = $2
55
- `;
56
- const accessCheck = await executeQuery(accessQuery, [inviterEmail, projectId, targetEmail]);
57
-
58
- if (accessCheck.rowCount === 0) {
59
- return createErrorResponse(404, 'Project or collaborator not found');
60
- }
61
-
62
- const data = accessCheck.rows[0];
63
-
64
- // Check permissions
65
- if (data.inviter_role !== 'owner' && data.inviter_role !== 'admin') {
66
- return createErrorResponse(403, 'Only project owners and admins can send invites');
67
- }
68
-
69
- // Check if already accepted
70
- if (data.accepted_at) {
71
- return createErrorResponse(400, 'Collaborator has already accepted the invitation');
72
- }
73
-
74
- // Generate invite token (simple UUID-based token)
75
- const inviteToken = `inv_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
76
-
77
- // Store invite token
78
- await executeQuery(`
79
- UPDATE rapport.project_collaborators
80
- SET invite_token = $1, invited_at = NOW()
81
- WHERE project_id = $2 AND email_address = $3
82
- `, [inviteToken, projectId, targetEmail]);
83
-
84
- // Build invite URL
85
- const appUrl = process.env.APP_URL || 'https://app.mindmeld.dev';
86
- const inviteUrl = `${appUrl}/invite/accept?token=${inviteToken}`;
87
-
88
- // Send email
89
- const inviterName = data.inviter_name || inviterEmail;
90
- const emailParams = {
91
- Source: process.env.EMAIL_FROM || 'noreply@mindmeld.dev',
92
- Destination: {
93
- ToAddresses: [targetEmail]
94
- },
95
- Message: {
96
- Subject: {
97
- Data: `You've been invited to collaborate on ${data.project_name}`,
98
- Charset: 'UTF-8'
99
- },
100
- Body: {
101
- Html: {
102
- Data: `
103
- <!DOCTYPE html>
104
- <html>
105
- <head>
106
- <meta charset="utf-8">
107
- <style>
108
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; }
109
- .container { max-width: 600px; margin: 0 auto; padding: 20px; }
110
- .header { text-align: center; margin-bottom: 30px; }
111
- .logo { font-size: 24px; font-weight: bold; color: #2563eb; }
112
- .content { background: #f8fafc; border-radius: 8px; padding: 30px; margin-bottom: 20px; }
113
- .button { display: inline-block; background: #2563eb; color: white; padding: 12px 24px; border-radius: 6px; text-decoration: none; font-weight: 500; }
114
- .button:hover { background: #1d4ed8; }
115
- .footer { text-align: center; color: #64748b; font-size: 14px; }
116
- .project-name { font-size: 20px; font-weight: 600; color: #1e293b; }
117
- .role-badge { display: inline-block; background: #e0e7ff; color: #3730a3; padding: 4px 12px; border-radius: 20px; font-size: 14px; }
118
- </style>
119
- </head>
120
- <body>
121
- <div class="container">
122
- <div class="header">
123
- <div class="logo">MindMeld</div>
124
- </div>
125
- <div class="content">
126
- <p>Hi there,</p>
127
- <p><strong>${inviterName}</strong> has invited you to collaborate on a project:</p>
128
- <p class="project-name">${data.project_name}</p>
129
- ${data.description ? `<p style="color: #64748b;">${data.description}</p>` : ''}
130
- <p>Your role: <span class="role-badge">${data.target_role}</span></p>
131
- <p style="margin-top: 30px;">
132
- <a href="${inviteUrl}" class="button">Accept Invitation</a>
133
- </p>
134
- <p style="margin-top: 20px; font-size: 14px; color: #64748b;">
135
- Or copy this link: ${inviteUrl}
136
- </p>
137
- </div>
138
- <div class="footer">
139
- <p>MindMeld - Collaboration memory that compounds</p>
140
- <p>Powered by Equilateral AI</p>
141
- </div>
142
- </div>
143
- </body>
144
- </html>
145
- `,
146
- Charset: 'UTF-8'
147
- },
148
- Text: {
149
- Data: `
150
- You've been invited to collaborate on ${data.project_name}
151
-
152
- ${inviterName} has invited you to join their project on MindMeld.
153
-
154
- Project: ${data.project_name}
155
- ${data.description ? `Description: ${data.description}` : ''}
156
- Your role: ${data.target_role}
157
-
158
- Accept the invitation: ${inviteUrl}
159
-
160
- ---
161
- MindMeld - Collaboration memory that compounds
162
- Powered by Equilateral AI
163
- `,
164
- Charset: 'UTF-8'
165
- }
166
- }
167
- }
168
- };
169
-
170
- try {
171
- await ses.send(new SendEmailCommand(emailParams));
172
- } catch (sesError) {
173
- console.error('SES Error:', sesError);
174
- // Don't fail the request, just note that email failed
175
- return createSuccessResponse(
176
- {
177
- Records: [{
178
- email: targetEmail,
179
- project_id: projectId,
180
- invite_url: inviteUrl,
181
- email_sent: false,
182
- email_error: 'Failed to send email. Share the invite link manually.'
183
- }]
184
- },
185
- 'Invite created but email failed to send',
186
- {
187
- Total_Records: 1,
188
- Request_ID,
189
- Timestamp: new Date().toISOString()
190
- }
191
- );
192
- }
193
-
194
- return createSuccessResponse(
195
- {
196
- Records: [{
197
- email: targetEmail,
198
- project_id: projectId,
199
- project_name: data.project_name,
200
- role: data.target_role,
201
- invite_url: inviteUrl,
202
- email_sent: true
203
- }]
204
- },
205
- 'Invitation sent successfully',
206
- {
207
- Total_Records: 1,
208
- Request_ID,
209
- Timestamp: new Date().toISOString()
210
- }
211
- );
212
-
213
- } catch (error) {
214
- console.error('Handler Error:', error);
215
- return handleError(error);
216
- }
217
- }
218
-
219
- exports.handler = wrapHandler(inviteCollaborator);
@@ -1,82 +0,0 @@
1
- /**
2
- * Collaborator List Handler
3
- * Lists all collaborators for a project
4
- *
5
- * GET /api/collaborators?project_id=xxx
6
- * Auth: Cognito JWT required
7
- */
8
-
9
- const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError, verifyProjectAccess } = require('./helpers');
10
-
11
- /**
12
- * List project collaborators
13
- * Requires collaborator access to project
14
- */
15
- async function listCollaborators({ queryStringParameters: queryParams = {}, requestContext }) {
16
- try {
17
- const Request_ID = requestContext.requestId;
18
- const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
19
- const projectId = queryParams.project_id;
20
-
21
- if (!email) {
22
- return createErrorResponse(401, 'Authentication required');
23
- }
24
-
25
- if (!projectId) {
26
- return createErrorResponse(400, 'projectId is required');
27
- }
28
-
29
- // Verify user has access to project (collaborator or company member)
30
- const projectAccess = await verifyProjectAccess(projectId, email);
31
- if (!projectAccess) {
32
- return createErrorResponse(403, 'You do not have access to this project');
33
- }
34
-
35
- // Get all collaborators
36
- const query = `
37
- SELECT
38
- pc.email_address,
39
- pc.role,
40
- pc.invited_by,
41
- pc.invited_at,
42
- pc.accepted_at,
43
- pc.is_external,
44
- u.full_name,
45
- CASE
46
- WHEN pc.accepted_at IS NOT NULL THEN 'active'
47
- WHEN pc.invited_at IS NOT NULL THEN 'pending'
48
- ELSE 'unknown'
49
- END as status
50
- FROM rapport.project_collaborators pc
51
- LEFT JOIN rapport.users u ON u.email_address = pc.email_address
52
- WHERE pc.project_id = $1
53
- ORDER BY
54
- CASE pc.role
55
- WHEN 'owner' THEN 1
56
- WHEN 'admin' THEN 2
57
- WHEN 'collaborator' THEN 3
58
- WHEN 'viewer' THEN 4
59
- ELSE 5
60
- END,
61
- pc.invited_at ASC
62
- `;
63
-
64
- const result = await executeQuery(query, [projectId]);
65
-
66
- return createSuccessResponse(
67
- { Records: result.rows },
68
- 'Collaborators retrieved',
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(listCollaborators);