@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.
Files changed (139) hide show
  1. package/hooks/session-start.js +312 -85
  2. package/package.json +20 -14
  3. package/scripts/init-project.js +9 -23
  4. package/src/client/dbShim.js +16 -0
  5. package/src/core/AuthManager.js +3 -2
  6. package/src/handlers/helpers/dbOperations.js +9 -46
  7. package/src/index.js +2 -217
  8. package/src/utils/piiMask.js +16 -0
  9. package/scripts/harvest.js +0 -601
  10. package/scripts/inject.js +0 -409
  11. package/scripts/mcp-bridge.js +0 -220
  12. package/scripts/repo-analyzer.js +0 -870
  13. package/scripts/standards.js +0 -285
  14. package/src/collaboration/CollaborationPrompt.js +0 -460
  15. package/src/core/AlertEngine.js +0 -813
  16. package/src/core/AlertNotifier.js +0 -363
  17. package/src/core/CorrelationAnalyzer.js +0 -931
  18. package/src/core/CrossReferenceEngine.js +0 -624
  19. package/src/core/CurationEngine.js +0 -688
  20. package/src/core/DeprecationScheduler.js +0 -183
  21. package/src/core/LoadBearingDetector.js +0 -242
  22. package/src/core/NotificationService.js +0 -1032
  23. package/src/core/RapportOrchestrator.js +0 -632
  24. package/src/core/RelevanceDetector.js +0 -694
  25. package/src/core/StandardLifecycle.js +0 -244
  26. package/src/core/StandardsIngestion.js +0 -991
  27. package/src/core/TeamLoadBearingDetector.js +0 -431
  28. package/src/core/parsers/adrParser.js +0 -479
  29. package/src/core/parsers/cursorRulesParser.js +0 -564
  30. package/src/core/parsers/eslintParser.js +0 -439
  31. package/src/database/dbOperations.js +0 -105
  32. package/src/handlers/activity/activityGetMe.js +0 -98
  33. package/src/handlers/activity/activityGetTeam.js +0 -175
  34. package/src/handlers/admin/adminSetup.js +0 -216
  35. package/src/handlers/alerts/alertsAcknowledge.js +0 -92
  36. package/src/handlers/alerts/alertsGet.js +0 -250
  37. package/src/handlers/analytics/activitySummaryGet.js +0 -234
  38. package/src/handlers/analytics/coachingGet.js +0 -361
  39. package/src/handlers/analytics/convergenceGet.js +0 -236
  40. package/src/handlers/analytics/developerScoreGet.js +0 -137
  41. package/src/handlers/collaborators/collaboratorAdd.js +0 -200
  42. package/src/handlers/collaborators/collaboratorInvite.js +0 -219
  43. package/src/handlers/collaborators/collaboratorList.js +0 -82
  44. package/src/handlers/collaborators/collaboratorRemove.js +0 -128
  45. package/src/handlers/collaborators/inviteAccept.js +0 -122
  46. package/src/handlers/company/companyUsersDelete.js +0 -141
  47. package/src/handlers/company/companyUsersGet.js +0 -90
  48. package/src/handlers/company/companyUsersPost.js +0 -267
  49. package/src/handlers/company/companyUsersPut.js +0 -76
  50. package/src/handlers/context/contextGet.js +0 -57
  51. package/src/handlers/context/invariantsGet.js +0 -74
  52. package/src/handlers/context/loopsGet.js +0 -82
  53. package/src/handlers/context/notesCreate.js +0 -74
  54. package/src/handlers/context/purposeGet.js +0 -78
  55. package/src/handlers/correlations/correlationsDeveloperGet.js +0 -227
  56. package/src/handlers/correlations/correlationsGet.js +0 -93
  57. package/src/handlers/correlations/correlationsProjectGet.js +0 -153
  58. package/src/handlers/enterprise/controlTowerGet.js +0 -224
  59. package/src/handlers/enterprise/enterpriseAuditGet.js +0 -108
  60. package/src/handlers/enterprise/enterpriseContributorsGet.js +0 -85
  61. package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +0 -53
  62. package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +0 -77
  63. package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +0 -71
  64. package/src/handlers/enterprise/enterpriseKnowledgeGet.js +0 -87
  65. package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +0 -122
  66. package/src/handlers/enterprise/enterpriseOnboardingComplete.js +0 -77
  67. package/src/handlers/enterprise/enterpriseOnboardingInvite.js +0 -138
  68. package/src/handlers/enterprise/enterpriseOnboardingSetup.js +0 -128
  69. package/src/handlers/enterprise/enterpriseOnboardingStatus.js +0 -88
  70. package/src/handlers/github/githubConnectionStatus.js +0 -49
  71. package/src/handlers/github/githubDiscoverPatterns.js +0 -621
  72. package/src/handlers/github/githubOAuthCallback.js +0 -178
  73. package/src/handlers/github/githubOAuthStart.js +0 -59
  74. package/src/handlers/github/githubPatternsReview.js +0 -76
  75. package/src/handlers/github/githubReposList.js +0 -105
  76. package/src/handlers/health/healthGet.js +0 -55
  77. package/src/handlers/helpers/auditLogger.js +0 -201
  78. package/src/handlers/helpers/checkSuperAdmin.js +0 -84
  79. package/src/handlers/helpers/decisionFrames.js +0 -29
  80. package/src/handlers/helpers/errorHandler.js +0 -49
  81. package/src/handlers/helpers/index.js +0 -138
  82. package/src/handlers/helpers/lambdaWrapper.js +0 -60
  83. package/src/handlers/helpers/mindmeldMcpCore.js +0 -1103
  84. package/src/handlers/helpers/predictiveCache.js +0 -51
  85. package/src/handlers/helpers/projectAccess.js +0 -88
  86. package/src/handlers/helpers/responseUtil.js +0 -55
  87. package/src/handlers/helpers/subscriptionTiers.js +0 -1168
  88. package/src/handlers/mcp/mcpHandler.js +0 -569
  89. package/src/handlers/mcp/mindmeldMcpHandler.js +0 -124
  90. package/src/handlers/mcp/mindmeldMcpStreamHandler.js +0 -342
  91. package/src/handlers/notifications/getPreferences.js +0 -84
  92. package/src/handlers/notifications/sendNotification.js +0 -170
  93. package/src/handlers/notifications/updatePreferences.js +0 -316
  94. package/src/handlers/patterns/patternEvaluatePromotionPost.js +0 -173
  95. package/src/handlers/patterns/patternUsagePost.js +0 -182
  96. package/src/handlers/patterns/patternViolationPost.js +0 -185
  97. package/src/handlers/projects/projectCreate.js +0 -248
  98. package/src/handlers/projects/projectDelete.js +0 -82
  99. package/src/handlers/projects/projectGet.js +0 -95
  100. package/src/handlers/projects/projectUpdate.js +0 -117
  101. package/src/handlers/reports/aiLeverage.js +0 -210
  102. package/src/handlers/reports/engineeringInvestment.js +0 -132
  103. package/src/handlers/reports/riskForecast.js +0 -206
  104. package/src/handlers/reports/standardsRoi.js +0 -254
  105. package/src/handlers/scheduled/analyzeCorrelations.js +0 -178
  106. package/src/handlers/scheduled/analyzeGitHistory.js +0 -510
  107. package/src/handlers/scheduled/generateAlerts.js +0 -135
  108. package/src/handlers/scheduled/maturityUpdateJob.js +0 -166
  109. package/src/handlers/scheduled/refreshActivity.js +0 -21
  110. package/src/handlers/scheduled/scanCompliance.js +0 -334
  111. package/src/handlers/sessions/sessionEndPost.js +0 -180
  112. package/src/handlers/sessions/sessionStandardsPost.js +0 -171
  113. package/src/handlers/standards/catalogGet.js +0 -185
  114. package/src/handlers/standards/catalogSync.js +0 -120
  115. package/src/handlers/standards/discoveriesGet.js +0 -89
  116. package/src/handlers/standards/projectStandardsGet.js +0 -129
  117. package/src/handlers/standards/projectStandardsPut.js +0 -151
  118. package/src/handlers/standards/standardsAuditGet.js +0 -65
  119. package/src/handlers/standards/standardsParseUpload.js +0 -149
  120. package/src/handlers/standards/standardsRelevantPost.js +0 -405
  121. package/src/handlers/standards/standardsTransition.js +0 -161
  122. package/src/handlers/stripe/addonManagePost.js +0 -240
  123. package/src/handlers/stripe/billingPortalPost.js +0 -93
  124. package/src/handlers/stripe/enterpriseCheckoutPost.js +0 -272
  125. package/src/handlers/stripe/seatsUpdatePost.js +0 -185
  126. package/src/handlers/stripe/subscriptionCancelDelete.js +0 -169
  127. package/src/handlers/stripe/subscriptionCreatePost.js +0 -221
  128. package/src/handlers/stripe/subscriptionUpdatePut.js +0 -163
  129. package/src/handlers/stripe/webhookPost.js +0 -482
  130. package/src/handlers/user/apiTokenCreate.js +0 -71
  131. package/src/handlers/user/apiTokenList.js +0 -64
  132. package/src/handlers/user/userSplashAck.js +0 -91
  133. package/src/handlers/user/userSplashGet.js +0 -211
  134. package/src/handlers/users/cognitoPostConfirmation.js +0 -186
  135. package/src/handlers/users/cognitoPreSignUp.js +0 -114
  136. package/src/handlers/users/userEntitlementsGet.js +0 -89
  137. package/src/handlers/users/userGet.js +0 -118
  138. package/src/handlers/users/userProfilePut.js +0 -77
  139. 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);