@equilateral_ai/mindmeld 3.1.2 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/hooks/README.md +46 -4
- package/hooks/pre-compact.js +115 -12
- package/hooks/session-end.js +292 -0
- package/hooks/session-start.js +302 -25
- package/package.json +5 -2
- package/scripts/auth-login.js +53 -0
- package/scripts/harvest.js +59 -19
- package/scripts/init-project.js +134 -374
- package/scripts/inject.js +30 -9
- package/scripts/repo-analyzer.js +870 -0
- package/src/core/AuthManager.js +498 -0
- package/src/core/CrossReferenceEngine.js +624 -0
- package/src/core/DeprecationScheduler.js +183 -0
- package/src/core/LLMPatternDetector.js +218 -0
- package/src/core/RapportOrchestrator.js +186 -0
- package/src/core/RelevanceDetector.js +32 -2
- package/src/core/StandardLifecycle.js +244 -0
- package/src/core/StandardsIngestion.js +341 -28
- package/src/core/parsers/adrParser.js +479 -0
- package/src/core/parsers/cursorRulesParser.js +564 -0
- package/src/core/parsers/eslintParser.js +439 -0
- package/src/handlers/alerts/alertsAcknowledge.js +4 -3
- package/src/handlers/analytics/activitySummaryGet.js +235 -0
- package/src/handlers/analytics/coachingGet.js +361 -0
- package/src/handlers/analytics/developerScoreGet.js +207 -0
- package/src/handlers/collaborators/collaboratorAdd.js +4 -5
- package/src/handlers/collaborators/collaboratorInvite.js +6 -5
- package/src/handlers/collaborators/collaboratorList.js +3 -3
- package/src/handlers/collaborators/collaboratorRemove.js +5 -4
- package/src/handlers/correlations/correlationsDeveloperGet.js +12 -11
- package/src/handlers/correlations/correlationsGet.js +1 -1
- package/src/handlers/correlations/correlationsProjectGet.js +7 -6
- package/src/handlers/enterprise/enterpriseAuditGet.js +108 -0
- package/src/handlers/enterprise/enterpriseContributorsGet.js +85 -0
- package/src/handlers/enterprise/enterpriseKnowledgeCategoriesGet.js +53 -0
- package/src/handlers/enterprise/enterpriseKnowledgeCreate.js +77 -0
- package/src/handlers/enterprise/enterpriseKnowledgeDelete.js +71 -0
- package/src/handlers/enterprise/enterpriseKnowledgeGet.js +87 -0
- package/src/handlers/enterprise/enterpriseKnowledgeUpdate.js +122 -0
- package/src/handlers/enterprise/enterpriseOnboardingComplete.js +77 -0
- package/src/handlers/enterprise/enterpriseOnboardingInvite.js +138 -0
- package/src/handlers/enterprise/enterpriseOnboardingSetup.js +89 -0
- package/src/handlers/enterprise/enterpriseOnboardingStatus.js +90 -0
- package/src/handlers/github/githubConnectionStatus.js +1 -1
- package/src/handlers/github/githubDiscoverPatterns.js +264 -5
- package/src/handlers/github/githubOAuthCallback.js +14 -2
- package/src/handlers/github/githubOAuthStart.js +1 -1
- package/src/handlers/github/githubPatternsReview.js +1 -1
- package/src/handlers/github/githubReposList.js +1 -1
- package/src/handlers/helpers/auditLogger.js +201 -0
- package/src/handlers/helpers/index.js +19 -1
- package/src/handlers/helpers/lambdaWrapper.js +1 -1
- package/src/handlers/notifications/sendNotification.js +1 -1
- package/src/handlers/projects/projectCreate.js +28 -1
- package/src/handlers/projects/projectDelete.js +3 -3
- package/src/handlers/projects/projectUpdate.js +4 -5
- package/src/handlers/scheduled/analyzeCorrelations.js +3 -3
- package/src/handlers/scheduled/generateAlerts.js +1 -1
- package/src/handlers/standards/catalogGet.js +185 -0
- package/src/handlers/standards/catalogSync.js +120 -0
- package/src/handlers/standards/projectStandardsGet.js +135 -0
- package/src/handlers/standards/projectStandardsPut.js +131 -0
- package/src/handlers/standards/standardsAuditGet.js +65 -0
- package/src/handlers/standards/standardsParseUpload.js +153 -0
- package/src/handlers/standards/standardsRelevantPost.js +213 -0
- package/src/handlers/standards/standardsTransition.js +64 -0
- package/src/handlers/user/userSplashAck.js +91 -0
- package/src/handlers/user/userSplashGet.js +194 -0
- package/src/handlers/users/userProfilePut.js +77 -0
- package/src/index.js +37 -29
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Project Update Handler
|
|
3
3
|
* Updates project metadata
|
|
4
4
|
*
|
|
5
|
-
* PUT /api/projects
|
|
6
|
-
* Body: { project_name, description, private }
|
|
5
|
+
* PUT /api/projects
|
|
6
|
+
* Body: { project_id, project_name, description, private }
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
|
|
@@ -12,14 +12,13 @@ const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, h
|
|
|
12
12
|
* Update project
|
|
13
13
|
* Requires owner or admin role on project
|
|
14
14
|
*/
|
|
15
|
-
async function updateProject({
|
|
15
|
+
async function updateProject({ body: requestBody = {}, requestContext }) {
|
|
16
16
|
try {
|
|
17
17
|
const Request_ID = requestContext.requestId;
|
|
18
18
|
// REST API: requestContext.authorizer.claims.email
|
|
19
19
|
// HTTP API: requestContext.authorizer.jwt.claims.email
|
|
20
20
|
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
21
|
-
const { projectId } =
|
|
22
|
-
const { project_name, description, private: isPrivate, repo_url } = requestBody;
|
|
21
|
+
const { project_id: projectId, project_name, description, private: isPrivate, repo_url } = requestBody;
|
|
23
22
|
|
|
24
23
|
if (!projectId) {
|
|
25
24
|
return createErrorResponse(400, 'projectId is required');
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
const { wrapHandler, executeQuery, createSuccessResponse } = require('./helpers');
|
|
16
|
-
const { CorrelationAnalyzer } = require('
|
|
16
|
+
const { CorrelationAnalyzer } = require('./core/CorrelationAnalyzer');
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Main handler - wrapped with wrapHandler for consistent error handling
|
|
@@ -105,9 +105,9 @@ async function createStrugglingDeveloperAlerts(companyId, developers) {
|
|
|
105
105
|
SELECT 1 FROM rapport.attention_alerts
|
|
106
106
|
WHERE email_address = $1
|
|
107
107
|
AND alert_type = 'low_conversion'
|
|
108
|
-
AND (status = 'active' OR created_at > NOW() - INTERVAL '
|
|
108
|
+
AND (status = 'active' OR created_at > NOW() - $2 * INTERVAL '1 hour')
|
|
109
109
|
LIMIT 1
|
|
110
|
-
`, [dev.email]);
|
|
110
|
+
`, [dev.email, cooldownHours]);
|
|
111
111
|
|
|
112
112
|
if (existingCheck.rows.length > 0) {
|
|
113
113
|
console.log(`[AnalyzeCorrelations] Skipping alert for ${dev.email} - already exists`);
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
const { wrapHandler, createSuccessResponse, executeQuery } = require('./helpers');
|
|
18
|
-
const { AlertEngine, setExecuteQuery } = require('
|
|
18
|
+
const { AlertEngine, setExecuteQuery } = require('./core/AlertEngine');
|
|
19
19
|
|
|
20
20
|
// Initialize AlertEngine with database connection
|
|
21
21
|
setExecuteQuery(executeQuery);
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standards Catalog Get Handler
|
|
3
|
+
* Returns the standards catalog manifest based on subscription tier
|
|
4
|
+
*
|
|
5
|
+
* GET /api/standards/catalog
|
|
6
|
+
* Auth: Cognito JWT required
|
|
7
|
+
* Query params:
|
|
8
|
+
* - catalog_id: Override catalog selection
|
|
9
|
+
* - tech_stack: Comma-separated list of detected technologies for recommendations
|
|
10
|
+
*
|
|
11
|
+
* Enterprise tier: Full proprietary standards (equilateral-v1)
|
|
12
|
+
* Other tiers: Open standards only (equilateral-open-v1)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
16
|
+
|
|
17
|
+
// Enterprise tiers that get full proprietary standards
|
|
18
|
+
const ENTERPRISE_TIERS = ['enterprise', 'enterprise_private', 'enterprise_team'];
|
|
19
|
+
|
|
20
|
+
// Tech stack to category recommendations mapping
|
|
21
|
+
const TECH_TO_CATEGORIES = {
|
|
22
|
+
// Languages/Frameworks
|
|
23
|
+
'react': ['frontend-development'],
|
|
24
|
+
'vue': ['frontend-development'],
|
|
25
|
+
'angular': ['frontend-development'],
|
|
26
|
+
'typescript': ['frontend-development'],
|
|
27
|
+
'node.js': ['serverless-saas-aws'],
|
|
28
|
+
'nodejs': ['serverless-saas-aws'],
|
|
29
|
+
'express': ['serverless-saas-aws'],
|
|
30
|
+
|
|
31
|
+
// Infrastructure
|
|
32
|
+
'aws sam': ['serverless-saas-aws'],
|
|
33
|
+
'aws lambda': ['serverless-saas-aws'],
|
|
34
|
+
'lambda': ['serverless-saas-aws'],
|
|
35
|
+
'serverless': ['serverless-saas-aws'],
|
|
36
|
+
'cloudformation': ['serverless-saas-aws'],
|
|
37
|
+
'api gateway': ['serverless-saas-aws'],
|
|
38
|
+
|
|
39
|
+
// Databases
|
|
40
|
+
'postgresql': ['database-patterns', 'memory-systems'],
|
|
41
|
+
'postgres': ['database-patterns', 'memory-systems'],
|
|
42
|
+
'dynamodb': ['serverless-saas-aws'],
|
|
43
|
+
|
|
44
|
+
// Testing
|
|
45
|
+
'jest': ['frontend-development'],
|
|
46
|
+
|
|
47
|
+
// Multi-agent
|
|
48
|
+
'claude': ['multi-agent-orchestration'],
|
|
49
|
+
'openai': ['multi-agent-orchestration'],
|
|
50
|
+
'langchain': ['multi-agent-orchestration'],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get recommended categories based on detected tech stack
|
|
55
|
+
* @param {string} techStackParam - Comma-separated tech stack string
|
|
56
|
+
* @returns {string[]} - Unique list of recommended category keys
|
|
57
|
+
*/
|
|
58
|
+
function getRecommendations(techStackParam) {
|
|
59
|
+
if (!techStackParam) return [];
|
|
60
|
+
|
|
61
|
+
const technologies = techStackParam.split(',').map(t => t.trim().toLowerCase());
|
|
62
|
+
const recommendations = new Set();
|
|
63
|
+
|
|
64
|
+
for (const tech of technologies) {
|
|
65
|
+
const categories = TECH_TO_CATEGORIES[tech];
|
|
66
|
+
if (categories) {
|
|
67
|
+
categories.forEach(cat => recommendations.add(cat));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return Array.from(recommendations);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function getCatalog({ requestContext, queryStringParameters }) {
|
|
75
|
+
try {
|
|
76
|
+
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
77
|
+
|
|
78
|
+
if (!email) {
|
|
79
|
+
return createErrorResponse(401, 'Authentication required');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Get user's subscription tier
|
|
83
|
+
const userResult = await executeQuery(`
|
|
84
|
+
SELECT c.subscription_tier
|
|
85
|
+
FROM rapport.users u
|
|
86
|
+
LEFT JOIN rapport.clients c ON u.client_id = c.client_id
|
|
87
|
+
WHERE u.email_address = $1 AND u.active = true
|
|
88
|
+
`, [email]);
|
|
89
|
+
|
|
90
|
+
const subscriptionTier = userResult.rows[0]?.subscription_tier || 'free';
|
|
91
|
+
const isEnterprise = ENTERPRISE_TIERS.includes(subscriptionTier);
|
|
92
|
+
|
|
93
|
+
// Get tech stack recommendations if provided
|
|
94
|
+
const techStack = queryStringParameters?.tech_stack;
|
|
95
|
+
const recommendations = getRecommendations(techStack);
|
|
96
|
+
|
|
97
|
+
// Determine which catalog to return based on tier
|
|
98
|
+
// Enterprise gets full catalog, others get open standards
|
|
99
|
+
const catalogId = queryStringParameters?.catalog_id ||
|
|
100
|
+
(isEnterprise ? 'equilateral-v1' : 'equilateral-open-v1');
|
|
101
|
+
|
|
102
|
+
const result = await executeQuery(`
|
|
103
|
+
SELECT
|
|
104
|
+
catalog_id,
|
|
105
|
+
version,
|
|
106
|
+
source_url,
|
|
107
|
+
updated_at,
|
|
108
|
+
total_standards,
|
|
109
|
+
total_layers,
|
|
110
|
+
categories,
|
|
111
|
+
layers,
|
|
112
|
+
critical_alerts
|
|
113
|
+
FROM rapport.standards_catalog
|
|
114
|
+
WHERE catalog_id = $1
|
|
115
|
+
`, [catalogId]);
|
|
116
|
+
|
|
117
|
+
if (result.rowCount === 0) {
|
|
118
|
+
// Fall back to open standards if requested catalog not found
|
|
119
|
+
const fallbackResult = await executeQuery(`
|
|
120
|
+
SELECT
|
|
121
|
+
catalog_id,
|
|
122
|
+
version,
|
|
123
|
+
source_url,
|
|
124
|
+
updated_at,
|
|
125
|
+
total_standards,
|
|
126
|
+
total_layers,
|
|
127
|
+
categories,
|
|
128
|
+
layers,
|
|
129
|
+
critical_alerts
|
|
130
|
+
FROM rapport.standards_catalog
|
|
131
|
+
WHERE catalog_id = 'equilateral-open-v1'
|
|
132
|
+
`);
|
|
133
|
+
|
|
134
|
+
if (fallbackResult.rowCount === 0) {
|
|
135
|
+
return createErrorResponse(404, 'Standards catalog not found', {
|
|
136
|
+
catalog_id: catalogId,
|
|
137
|
+
hint: 'Run migration or sync catalog first'
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const catalog = fallbackResult.rows[0];
|
|
142
|
+
return createSuccessResponse({
|
|
143
|
+
catalog_id: catalog.catalog_id,
|
|
144
|
+
version: catalog.version,
|
|
145
|
+
source_url: catalog.source_url,
|
|
146
|
+
updated_at: catalog.updated_at,
|
|
147
|
+
subscription_tier: subscriptionTier,
|
|
148
|
+
is_enterprise: false,
|
|
149
|
+
recommendations,
|
|
150
|
+
summary: {
|
|
151
|
+
total_standards: catalog.total_standards,
|
|
152
|
+
total_layers: catalog.total_layers
|
|
153
|
+
},
|
|
154
|
+
categories: catalog.categories,
|
|
155
|
+
layers: catalog.layers,
|
|
156
|
+
critical_alerts: catalog.critical_alerts
|
|
157
|
+
}, 'Open standards catalog retrieved');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const catalog = result.rows[0];
|
|
161
|
+
|
|
162
|
+
return createSuccessResponse({
|
|
163
|
+
catalog_id: catalog.catalog_id,
|
|
164
|
+
version: catalog.version,
|
|
165
|
+
source_url: catalog.source_url,
|
|
166
|
+
updated_at: catalog.updated_at,
|
|
167
|
+
subscription_tier: subscriptionTier,
|
|
168
|
+
is_enterprise: isEnterprise,
|
|
169
|
+
recommendations,
|
|
170
|
+
summary: {
|
|
171
|
+
total_standards: catalog.total_standards,
|
|
172
|
+
total_layers: catalog.total_layers
|
|
173
|
+
},
|
|
174
|
+
categories: catalog.categories,
|
|
175
|
+
layers: catalog.layers,
|
|
176
|
+
critical_alerts: catalog.critical_alerts
|
|
177
|
+
}, isEnterprise ? 'Full standards catalog retrieved' : 'Open standards catalog retrieved');
|
|
178
|
+
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error('Catalog Get Error:', error);
|
|
181
|
+
return createErrorResponse(500, 'Failed to retrieve standards catalog');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
exports.handler = wrapHandler(getCatalog);
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standards Catalog Sync Handler
|
|
3
|
+
* Imports/updates the standards catalog from manifest.json
|
|
4
|
+
*
|
|
5
|
+
* POST /api/standards/catalog/sync
|
|
6
|
+
* Body: { manifest } - The manifest.json content
|
|
7
|
+
* Auth: Cognito JWT required (admin only)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
11
|
+
|
|
12
|
+
async function syncCatalog({ body, requestContext }) {
|
|
13
|
+
try {
|
|
14
|
+
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
15
|
+
|
|
16
|
+
if (!email) {
|
|
17
|
+
return createErrorResponse(401, 'Authentication required');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { manifest, catalog_id = 'equilateral-v1', source_url } = body || {};
|
|
21
|
+
|
|
22
|
+
if (!manifest) {
|
|
23
|
+
return createErrorResponse(400, 'manifest is required');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Validate manifest structure
|
|
27
|
+
if (!manifest.version || !manifest.categories || !manifest.layers) {
|
|
28
|
+
return createErrorResponse(400, 'Invalid manifest: missing version, categories, or layers');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Extract metadata
|
|
32
|
+
const totalStandards = manifest.metadata?.totalStandards || 0;
|
|
33
|
+
const totalLayers = manifest.layers?.length || 0;
|
|
34
|
+
|
|
35
|
+
// Build categories object with descriptions
|
|
36
|
+
const categories = {};
|
|
37
|
+
if (manifest.categories) {
|
|
38
|
+
for (const [key, description] of Object.entries(manifest.categories)) {
|
|
39
|
+
categories[key] = {
|
|
40
|
+
name: key.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase()),
|
|
41
|
+
description: description,
|
|
42
|
+
// Count files in this category from layers
|
|
43
|
+
standards_count: 0
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Count standards per category from layers
|
|
49
|
+
if (manifest.layers) {
|
|
50
|
+
for (const layer of manifest.layers) {
|
|
51
|
+
if (layer.files) {
|
|
52
|
+
for (const file of layer.files) {
|
|
53
|
+
const category = file.split('/')[0];
|
|
54
|
+
if (categories[category]) {
|
|
55
|
+
categories[category].standards_count++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Upsert catalog
|
|
63
|
+
const result = await executeQuery(`
|
|
64
|
+
INSERT INTO rapport.standards_catalog (
|
|
65
|
+
catalog_id,
|
|
66
|
+
version,
|
|
67
|
+
source_url,
|
|
68
|
+
manifest,
|
|
69
|
+
total_standards,
|
|
70
|
+
total_layers,
|
|
71
|
+
categories,
|
|
72
|
+
layers,
|
|
73
|
+
critical_alerts,
|
|
74
|
+
updated_at
|
|
75
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW())
|
|
76
|
+
ON CONFLICT (catalog_id) DO UPDATE SET
|
|
77
|
+
version = EXCLUDED.version,
|
|
78
|
+
source_url = EXCLUDED.source_url,
|
|
79
|
+
manifest = EXCLUDED.manifest,
|
|
80
|
+
total_standards = EXCLUDED.total_standards,
|
|
81
|
+
total_layers = EXCLUDED.total_layers,
|
|
82
|
+
categories = EXCLUDED.categories,
|
|
83
|
+
layers = EXCLUDED.layers,
|
|
84
|
+
critical_alerts = EXCLUDED.critical_alerts,
|
|
85
|
+
updated_at = NOW()
|
|
86
|
+
RETURNING catalog_id, version, updated_at
|
|
87
|
+
`, [
|
|
88
|
+
catalog_id,
|
|
89
|
+
manifest.version,
|
|
90
|
+
source_url || manifest.metadata?.repository || null,
|
|
91
|
+
JSON.stringify(manifest),
|
|
92
|
+
totalStandards,
|
|
93
|
+
totalLayers,
|
|
94
|
+
JSON.stringify(categories),
|
|
95
|
+
JSON.stringify(manifest.layers),
|
|
96
|
+
JSON.stringify(manifest.criticalAlerts || [])
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
const synced = result.rows[0];
|
|
100
|
+
|
|
101
|
+
return createSuccessResponse({
|
|
102
|
+
catalog_id: synced.catalog_id,
|
|
103
|
+
version: synced.version,
|
|
104
|
+
updated_at: synced.updated_at,
|
|
105
|
+
summary: {
|
|
106
|
+
total_standards: totalStandards,
|
|
107
|
+
total_layers: totalLayers,
|
|
108
|
+
total_categories: Object.keys(categories).length,
|
|
109
|
+
critical_alerts: (manifest.criticalAlerts || []).length
|
|
110
|
+
},
|
|
111
|
+
categories: Object.keys(categories)
|
|
112
|
+
}, 'Standards catalog synced successfully');
|
|
113
|
+
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error('Catalog Sync Error:', error);
|
|
116
|
+
return createErrorResponse(500, 'Failed to sync standards catalog');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
exports.handler = wrapHandler(syncCatalog);
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Standards Get Handler
|
|
3
|
+
* Returns standards preferences for a project
|
|
4
|
+
*
|
|
5
|
+
* GET /api/projects/standards?project_id=xxx
|
|
6
|
+
* Auth: Cognito JWT required
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
10
|
+
|
|
11
|
+
async function getProjectStandards({ queryStringParameters, requestContext }) {
|
|
12
|
+
try {
|
|
13
|
+
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
14
|
+
|
|
15
|
+
if (!email) {
|
|
16
|
+
return createErrorResponse(401, 'Authentication required');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const projectId = (queryStringParameters || {}).project_id;
|
|
20
|
+
|
|
21
|
+
if (!projectId) {
|
|
22
|
+
return createErrorResponse(400, 'projectId is required');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Verify user has access to project
|
|
26
|
+
const accessResult = await executeQuery(`
|
|
27
|
+
SELECT pc.role, p.project_name
|
|
28
|
+
FROM rapport.project_collaborators pc
|
|
29
|
+
JOIN rapport.projects p ON pc.project_id = p.project_id
|
|
30
|
+
WHERE pc.project_id = $1 AND pc.email_address = $2
|
|
31
|
+
`, [projectId, email]);
|
|
32
|
+
|
|
33
|
+
if (accessResult.rowCount === 0) {
|
|
34
|
+
return createErrorResponse(403, 'Access denied to project');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const projectName = accessResult.rows[0].project_name;
|
|
38
|
+
|
|
39
|
+
// Get project standards preferences
|
|
40
|
+
const prefsResult = await executeQuery(`
|
|
41
|
+
SELECT
|
|
42
|
+
ps.catalog_id,
|
|
43
|
+
ps.enabled_categories,
|
|
44
|
+
ps.standard_overrides,
|
|
45
|
+
ps.critical_overrides,
|
|
46
|
+
ps.modified_by,
|
|
47
|
+
ps.modified_at,
|
|
48
|
+
sc.version as catalog_version,
|
|
49
|
+
sc.categories as available_categories,
|
|
50
|
+
sc.layers as available_layers,
|
|
51
|
+
sc.critical_alerts,
|
|
52
|
+
sc.total_standards
|
|
53
|
+
FROM rapport.project_standards ps
|
|
54
|
+
JOIN rapport.standards_catalog sc ON ps.catalog_id = sc.catalog_id
|
|
55
|
+
WHERE ps.project_id = $1
|
|
56
|
+
`, [projectId]);
|
|
57
|
+
|
|
58
|
+
// If no preferences exist, return defaults with catalog info
|
|
59
|
+
if (prefsResult.rowCount === 0) {
|
|
60
|
+
const catalogResult = await executeQuery(`
|
|
61
|
+
SELECT
|
|
62
|
+
catalog_id,
|
|
63
|
+
version,
|
|
64
|
+
categories,
|
|
65
|
+
layers,
|
|
66
|
+
critical_alerts,
|
|
67
|
+
total_standards
|
|
68
|
+
FROM rapport.standards_catalog
|
|
69
|
+
WHERE catalog_id = 'equilateral-v1'
|
|
70
|
+
`);
|
|
71
|
+
|
|
72
|
+
if (catalogResult.rowCount === 0) {
|
|
73
|
+
return createErrorResponse(404, 'Standards catalog not found');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const catalog = catalogResult.rows[0];
|
|
77
|
+
|
|
78
|
+
// Default: all categories enabled
|
|
79
|
+
const defaultCategories = {};
|
|
80
|
+
if (catalog.categories) {
|
|
81
|
+
for (const key of Object.keys(catalog.categories)) {
|
|
82
|
+
defaultCategories[key] = true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return createSuccessResponse({
|
|
87
|
+
project_id: projectId,
|
|
88
|
+
project_name: projectName,
|
|
89
|
+
catalog_id: catalog.catalog_id,
|
|
90
|
+
catalog_version: catalog.version,
|
|
91
|
+
has_custom_preferences: false,
|
|
92
|
+
preferences: {
|
|
93
|
+
enabled_categories: defaultCategories,
|
|
94
|
+
standard_overrides: {},
|
|
95
|
+
critical_overrides: {}
|
|
96
|
+
},
|
|
97
|
+
catalog: {
|
|
98
|
+
categories: catalog.categories,
|
|
99
|
+
layers: catalog.layers,
|
|
100
|
+
critical_alerts: catalog.critical_alerts,
|
|
101
|
+
total_standards: catalog.total_standards
|
|
102
|
+
}
|
|
103
|
+
}, 'Default standards preferences');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const prefs = prefsResult.rows[0];
|
|
107
|
+
|
|
108
|
+
return createSuccessResponse({
|
|
109
|
+
project_id: projectId,
|
|
110
|
+
project_name: projectName,
|
|
111
|
+
catalog_id: prefs.catalog_id,
|
|
112
|
+
catalog_version: prefs.catalog_version,
|
|
113
|
+
has_custom_preferences: true,
|
|
114
|
+
preferences: {
|
|
115
|
+
enabled_categories: prefs.enabled_categories || {},
|
|
116
|
+
standard_overrides: prefs.standard_overrides || {},
|
|
117
|
+
critical_overrides: prefs.critical_overrides || {}
|
|
118
|
+
},
|
|
119
|
+
catalog: {
|
|
120
|
+
categories: prefs.available_categories,
|
|
121
|
+
layers: prefs.available_layers,
|
|
122
|
+
critical_alerts: prefs.critical_alerts,
|
|
123
|
+
total_standards: prefs.total_standards
|
|
124
|
+
},
|
|
125
|
+
modified_by: prefs.modified_by,
|
|
126
|
+
modified_at: prefs.modified_at
|
|
127
|
+
}, 'Project standards preferences retrieved');
|
|
128
|
+
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error('Project Standards Get Error:', error);
|
|
131
|
+
return createErrorResponse(500, 'Failed to retrieve project standards');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
exports.handler = wrapHandler(getProjectStandards);
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Standards Put Handler
|
|
3
|
+
* Updates standards preferences for a project
|
|
4
|
+
*
|
|
5
|
+
* PUT /api/projects/standards
|
|
6
|
+
* Body: { project_id, enabled_categories, standard_overrides, critical_overrides }
|
|
7
|
+
* Auth: Cognito JWT required
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
11
|
+
|
|
12
|
+
async function updateProjectStandards({ body, requestContext }) {
|
|
13
|
+
try {
|
|
14
|
+
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
15
|
+
|
|
16
|
+
if (!email) {
|
|
17
|
+
return createErrorResponse(401, 'Authentication required');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const projectId = (body || {}).project_id;
|
|
21
|
+
|
|
22
|
+
if (!projectId) {
|
|
23
|
+
return createErrorResponse(400, 'projectId is required');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
enabled_categories,
|
|
28
|
+
standard_overrides,
|
|
29
|
+
critical_overrides,
|
|
30
|
+
catalog_id = 'equilateral-v1'
|
|
31
|
+
} = body || {};
|
|
32
|
+
|
|
33
|
+
// Verify user has admin access to project
|
|
34
|
+
const accessResult = await executeQuery(`
|
|
35
|
+
SELECT role FROM rapport.project_collaborators
|
|
36
|
+
WHERE project_id = $1 AND email_address = $2
|
|
37
|
+
`, [projectId, email]);
|
|
38
|
+
|
|
39
|
+
if (accessResult.rowCount === 0) {
|
|
40
|
+
return createErrorResponse(403, 'Access denied to project');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const role = accessResult.rows[0].role;
|
|
44
|
+
if (role !== 'owner' && role !== 'admin') {
|
|
45
|
+
return createErrorResponse(403, 'Admin access required to modify standards preferences');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Verify catalog exists
|
|
49
|
+
const catalogResult = await executeQuery(`
|
|
50
|
+
SELECT catalog_id, categories FROM rapport.standards_catalog
|
|
51
|
+
WHERE catalog_id = $1
|
|
52
|
+
`, [catalog_id]);
|
|
53
|
+
|
|
54
|
+
if (catalogResult.rowCount === 0) {
|
|
55
|
+
return createErrorResponse(404, 'Standards catalog not found', { catalog_id });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const availableCategories = Object.keys(catalogResult.rows[0].categories || {});
|
|
59
|
+
|
|
60
|
+
// Validate enabled_categories keys
|
|
61
|
+
if (enabled_categories) {
|
|
62
|
+
const invalidCategories = Object.keys(enabled_categories).filter(
|
|
63
|
+
cat => !availableCategories.includes(cat)
|
|
64
|
+
);
|
|
65
|
+
if (invalidCategories.length > 0) {
|
|
66
|
+
return createErrorResponse(400, 'Invalid category names', {
|
|
67
|
+
invalid: invalidCategories,
|
|
68
|
+
valid: availableCategories
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Upsert project standards
|
|
74
|
+
const result = await executeQuery(`
|
|
75
|
+
INSERT INTO rapport.project_standards (
|
|
76
|
+
project_id,
|
|
77
|
+
catalog_id,
|
|
78
|
+
enabled_categories,
|
|
79
|
+
standard_overrides,
|
|
80
|
+
critical_overrides,
|
|
81
|
+
created_by,
|
|
82
|
+
modified_by
|
|
83
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $6)
|
|
84
|
+
ON CONFLICT (project_id, catalog_id) DO UPDATE SET
|
|
85
|
+
enabled_categories = COALESCE($3, rapport.project_standards.enabled_categories),
|
|
86
|
+
standard_overrides = COALESCE($4, rapport.project_standards.standard_overrides),
|
|
87
|
+
critical_overrides = COALESCE($5, rapport.project_standards.critical_overrides),
|
|
88
|
+
modified_by = $6,
|
|
89
|
+
modified_at = NOW()
|
|
90
|
+
RETURNING *
|
|
91
|
+
`, [
|
|
92
|
+
projectId,
|
|
93
|
+
catalog_id,
|
|
94
|
+
enabled_categories ? JSON.stringify(enabled_categories) : null,
|
|
95
|
+
standard_overrides ? JSON.stringify(standard_overrides) : null,
|
|
96
|
+
critical_overrides ? JSON.stringify(critical_overrides) : null,
|
|
97
|
+
email
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
const updated = result.rows[0];
|
|
101
|
+
|
|
102
|
+
// Count enabled standards
|
|
103
|
+
const enabledCats = updated.enabled_categories || {};
|
|
104
|
+
const enabledCount = Object.values(enabledCats).filter(v => v === true).length;
|
|
105
|
+
const disabledCount = Object.values(enabledCats).filter(v => v === false).length;
|
|
106
|
+
|
|
107
|
+
return createSuccessResponse({
|
|
108
|
+
project_id: projectId,
|
|
109
|
+
catalog_id: updated.catalog_id,
|
|
110
|
+
preferences: {
|
|
111
|
+
enabled_categories: updated.enabled_categories,
|
|
112
|
+
standard_overrides: updated.standard_overrides,
|
|
113
|
+
critical_overrides: updated.critical_overrides
|
|
114
|
+
},
|
|
115
|
+
summary: {
|
|
116
|
+
categories_enabled: enabledCount,
|
|
117
|
+
categories_disabled: disabledCount,
|
|
118
|
+
standard_overrides: Object.keys(updated.standard_overrides || {}).length,
|
|
119
|
+
critical_overrides: Object.keys(updated.critical_overrides || {}).length
|
|
120
|
+
},
|
|
121
|
+
modified_by: updated.modified_by,
|
|
122
|
+
modified_at: updated.modified_at
|
|
123
|
+
}, 'Standards preferences updated');
|
|
124
|
+
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error('Project Standards Put Error:', error);
|
|
127
|
+
return createErrorResponse(500, 'Failed to update project standards');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
exports.handler = wrapHandler(updateProjectStandards);
|