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