@equilateral_ai/mindmeld 3.2.0 → 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 +87 -1
- package/hooks/session-end.js +292 -0
- package/hooks/session-start.js +292 -23
- package/package.json +4 -2
- package/scripts/auth-login.js +53 -0
- package/scripts/init-project.js +69 -375
- 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,9 @@
|
|
|
2
2
|
* Get Developer Correlations Handler
|
|
3
3
|
* Returns session-to-commit correlation data for a specific developer
|
|
4
4
|
*
|
|
5
|
-
* GET /api/correlations/developer
|
|
5
|
+
* GET /api/correlations/developer?email=xxx
|
|
6
6
|
* Query params:
|
|
7
|
+
* - email (required)
|
|
7
8
|
* - lookbackDays (optional, default: 30)
|
|
8
9
|
*
|
|
9
10
|
* Returns:
|
|
@@ -13,7 +14,7 @@
|
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
16
|
-
const { CorrelationAnalyzer } = require('
|
|
17
|
+
const { CorrelationAnalyzer } = require('./core/CorrelationAnalyzer');
|
|
17
18
|
|
|
18
19
|
exports.handler = wrapHandler(async (event, context) => {
|
|
19
20
|
// Extract user email from Cognito claims
|
|
@@ -22,8 +23,8 @@ exports.handler = wrapHandler(async (event, context) => {
|
|
|
22
23
|
return createErrorResponse(401, 'Unauthorized - no email in claims');
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
// Get target developer email from
|
|
26
|
-
const targetEmail = decodeURIComponent(event.
|
|
26
|
+
// Get target developer email from query parameters
|
|
27
|
+
const targetEmail = decodeURIComponent(event.queryStringParameters?.email || '');
|
|
27
28
|
if (!targetEmail) {
|
|
28
29
|
return createErrorResponse(400, 'Developer email is required');
|
|
29
30
|
}
|
|
@@ -121,12 +122,12 @@ async function getSessionHistory(email, lookbackDays, limit) {
|
|
|
121
122
|
FROM rapport.session_correlations sc
|
|
122
123
|
LEFT JOIN rapport.projects p ON sc.project_id = p.project_id
|
|
123
124
|
WHERE sc.email_address = $1
|
|
124
|
-
AND sc.session_started_at > NOW() - INTERVAL '
|
|
125
|
+
AND sc.session_started_at > NOW() - $2 * INTERVAL '1 day'
|
|
125
126
|
ORDER BY sc.session_started_at DESC
|
|
126
|
-
LIMIT $
|
|
127
|
+
LIMIT $3
|
|
127
128
|
`;
|
|
128
129
|
|
|
129
|
-
const result = await executeQuery(query, [email, limit]);
|
|
130
|
+
const result = await executeQuery(query, [email, lookbackDays, limit]);
|
|
130
131
|
|
|
131
132
|
return result.rows.map(row => ({
|
|
132
133
|
sessionId: row.session_id,
|
|
@@ -167,12 +168,12 @@ async function getWeeklyTrend(email, lookbackDays) {
|
|
|
167
168
|
SUM(sc.session_duration_seconds) / 3600.0 as session_hours
|
|
168
169
|
FROM rapport.session_correlations sc
|
|
169
170
|
WHERE sc.email_address = $1
|
|
170
|
-
AND sc.session_started_at > NOW() - INTERVAL '
|
|
171
|
+
AND sc.session_started_at > NOW() - $2 * INTERVAL '1 day'
|
|
171
172
|
GROUP BY DATE_TRUNC('week', sc.session_started_at)
|
|
172
173
|
ORDER BY week DESC
|
|
173
174
|
`;
|
|
174
175
|
|
|
175
|
-
const result = await executeQuery(query, [email]);
|
|
176
|
+
const result = await executeQuery(query, [email, lookbackDays]);
|
|
176
177
|
|
|
177
178
|
return result.rows.map(row => ({
|
|
178
179
|
week: row.week,
|
|
@@ -206,14 +207,14 @@ async function getPatternUsage(email, lookbackDays) {
|
|
|
206
207
|
JOIN rapport.patterns p ON pu.pattern_id = p.pattern_id
|
|
207
208
|
LEFT JOIN rapport.session_correlations sc ON pu.session_id = sc.session_id
|
|
208
209
|
WHERE pu.email_address = $1
|
|
209
|
-
AND pu.used_at > NOW() - INTERVAL '
|
|
210
|
+
AND pu.used_at > NOW() - $2 * INTERVAL '1 day'
|
|
210
211
|
GROUP BY p.pattern_id, p.intent, p.maturity
|
|
211
212
|
HAVING COUNT(DISTINCT pu.session_id) >= 2
|
|
212
213
|
ORDER BY sessions_used DESC
|
|
213
214
|
LIMIT 10
|
|
214
215
|
`;
|
|
215
216
|
|
|
216
|
-
const result = await executeQuery(query, [email]);
|
|
217
|
+
const result = await executeQuery(query, [email, lookbackDays]);
|
|
217
218
|
|
|
218
219
|
return result.rows.map(row => ({
|
|
219
220
|
patternId: row.pattern_id,
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
16
|
-
const { CorrelationAnalyzer } = require('
|
|
16
|
+
const { CorrelationAnalyzer } = require('./core/CorrelationAnalyzer');
|
|
17
17
|
|
|
18
18
|
exports.handler = wrapHandler(async (event, context) => {
|
|
19
19
|
// Extract user email from Cognito claims
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Get Project Correlations Handler
|
|
3
3
|
* Returns session-to-commit correlation data for a specific project
|
|
4
4
|
*
|
|
5
|
-
* GET /api/correlations/project
|
|
5
|
+
* GET /api/correlations/project?project_id=xxx
|
|
6
6
|
* Query params:
|
|
7
|
+
* - project_id (required)
|
|
7
8
|
* - lookbackDays (optional, default: 30)
|
|
8
9
|
*
|
|
9
10
|
* Returns:
|
|
@@ -13,7 +14,7 @@
|
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse } = require('./helpers');
|
|
16
|
-
const { CorrelationAnalyzer } = require('
|
|
17
|
+
const { CorrelationAnalyzer } = require('./core/CorrelationAnalyzer');
|
|
17
18
|
|
|
18
19
|
exports.handler = wrapHandler(async (event, context) => {
|
|
19
20
|
// Extract user email from Cognito claims
|
|
@@ -22,8 +23,8 @@ exports.handler = wrapHandler(async (event, context) => {
|
|
|
22
23
|
return createErrorResponse(401, 'Unauthorized - no email in claims');
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
// Get project ID from
|
|
26
|
-
const projectId = event.
|
|
26
|
+
// Get project ID from query parameters
|
|
27
|
+
const projectId = event.queryStringParameters?.project_id;
|
|
27
28
|
if (!projectId) {
|
|
28
29
|
return createErrorResponse(400, 'Project ID is required');
|
|
29
30
|
}
|
|
@@ -98,12 +99,12 @@ async function getDeveloperBreakdown(projectId, lookbackDays) {
|
|
|
98
99
|
FROM rapport.session_correlations sc
|
|
99
100
|
JOIN "Users" u ON sc.email_address = u."Email_Address"
|
|
100
101
|
WHERE sc.project_id = $1
|
|
101
|
-
AND sc.session_started_at > NOW() - INTERVAL '
|
|
102
|
+
AND sc.session_started_at > NOW() - $2 * INTERVAL '1 day'
|
|
102
103
|
GROUP BY sc.email_address, u."User_Display_Name"
|
|
103
104
|
ORDER BY total_commits DESC NULLS LAST
|
|
104
105
|
`;
|
|
105
106
|
|
|
106
|
-
const result = await executeQuery(query, [projectId]);
|
|
107
|
+
const result = await executeQuery(query, [projectId, lookbackDays]);
|
|
107
108
|
|
|
108
109
|
return result.rows.map(row => ({
|
|
109
110
|
email: row.email_address,
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enterprise Audit Log Get Handler
|
|
3
|
+
*
|
|
4
|
+
* GET /api/enterprise/audit?page=&pageSize=&action=&resourceType=&startDate=&endDate=
|
|
5
|
+
* Returns: { Records: AuditLogEntry[] } + meta { Total_Records, Page, Page_Size, Request_ID, Timestamp }
|
|
6
|
+
* Auth: Cognito JWT required, enterprise tier only
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
|
|
10
|
+
|
|
11
|
+
async function getEnterpriseAudit({ queryStringParameters, requestContext }) {
|
|
12
|
+
try {
|
|
13
|
+
const Request_ID = requestContext.requestId;
|
|
14
|
+
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
15
|
+
if (!email) {
|
|
16
|
+
return createErrorResponse(401, 'Authentication required');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Look up client_id and verify enterprise tier
|
|
20
|
+
const userResult = await executeQuery(
|
|
21
|
+
`SELECT u.client_id, c.subscription_tier
|
|
22
|
+
FROM rapport.users u
|
|
23
|
+
JOIN rapport.clients c ON c.client_id = u.client_id
|
|
24
|
+
WHERE u.email_address = $1 LIMIT 1`,
|
|
25
|
+
[email]
|
|
26
|
+
);
|
|
27
|
+
if (!userResult.rows.length) {
|
|
28
|
+
return createErrorResponse(403, 'User not found');
|
|
29
|
+
}
|
|
30
|
+
if (userResult.rows[0].subscription_tier !== 'enterprise') {
|
|
31
|
+
return createErrorResponse(403, 'Enterprise subscription required');
|
|
32
|
+
}
|
|
33
|
+
const clientId = userResult.rows[0].client_id;
|
|
34
|
+
|
|
35
|
+
const params = queryStringParameters || {};
|
|
36
|
+
const page = Math.max(1, parseInt(params.page) || 1);
|
|
37
|
+
const pageSize = Math.min(100, Math.max(1, parseInt(params.pageSize) || 20));
|
|
38
|
+
const offset = (page - 1) * pageSize;
|
|
39
|
+
|
|
40
|
+
// Build WHERE clause
|
|
41
|
+
const conditions = ['client_id = $1'];
|
|
42
|
+
const values = [clientId];
|
|
43
|
+
let paramIndex = 2;
|
|
44
|
+
|
|
45
|
+
if (params.action) {
|
|
46
|
+
conditions.push(`event_type = $${paramIndex}`);
|
|
47
|
+
values.push(params.action);
|
|
48
|
+
paramIndex++;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (params.resourceType) {
|
|
52
|
+
conditions.push(`entity_type = $${paramIndex}`);
|
|
53
|
+
values.push(params.resourceType);
|
|
54
|
+
paramIndex++;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (params.startDate) {
|
|
58
|
+
conditions.push(`created_at >= $${paramIndex}`);
|
|
59
|
+
values.push(params.startDate);
|
|
60
|
+
paramIndex++;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (params.endDate) {
|
|
64
|
+
conditions.push(`created_at <= $${paramIndex}`);
|
|
65
|
+
values.push(params.endDate);
|
|
66
|
+
paramIndex++;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const whereClause = conditions.join(' AND ');
|
|
70
|
+
|
|
71
|
+
// Get total count
|
|
72
|
+
const countResult = await executeQuery(
|
|
73
|
+
`SELECT COUNT(*) as total FROM rapport.unified_audit_log WHERE ${whereClause}`,
|
|
74
|
+
values
|
|
75
|
+
);
|
|
76
|
+
const total = parseInt(countResult.rows[0].total);
|
|
77
|
+
|
|
78
|
+
// Get paginated records, mapping columns to frontend AuditLogEntry type
|
|
79
|
+
const dataResult = await executeQuery(
|
|
80
|
+
`SELECT
|
|
81
|
+
audit_id,
|
|
82
|
+
client_id,
|
|
83
|
+
actor_email,
|
|
84
|
+
event_type as action,
|
|
85
|
+
entity_type as resource_type,
|
|
86
|
+
entity_id as resource_id,
|
|
87
|
+
before_state as old_value,
|
|
88
|
+
after_state as new_value,
|
|
89
|
+
created_at
|
|
90
|
+
FROM rapport.unified_audit_log
|
|
91
|
+
WHERE ${whereClause}
|
|
92
|
+
ORDER BY created_at DESC
|
|
93
|
+
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
|
|
94
|
+
[...values, pageSize, offset]
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
return createSuccessResponse(
|
|
98
|
+
{ Records: dataResult.rows },
|
|
99
|
+
'Audit log retrieved',
|
|
100
|
+
{ Total_Records: total, Page: page, Page_Size: pageSize, Request_ID, Timestamp: new Date().toISOString() }
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return handleError(error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
exports.handler = wrapHandler(getEnterpriseAudit);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enterprise Contributors Get Handler
|
|
3
|
+
*
|
|
4
|
+
* GET /api/enterprise/contributors?period=
|
|
5
|
+
* Returns: { Records: [contributor stats] } + meta
|
|
6
|
+
* Auth: Cognito JWT required, enterprise tier only
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
|
|
10
|
+
|
|
11
|
+
async function getEnterpriseContributors({ queryStringParameters, requestContext }) {
|
|
12
|
+
try {
|
|
13
|
+
const Request_ID = requestContext.requestId;
|
|
14
|
+
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
15
|
+
if (!email) {
|
|
16
|
+
return createErrorResponse(401, 'Authentication required');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const userResult = await executeQuery(
|
|
20
|
+
`SELECT u.client_id, c.subscription_tier
|
|
21
|
+
FROM rapport.users u
|
|
22
|
+
JOIN rapport.clients c ON c.client_id = u.client_id
|
|
23
|
+
WHERE u.email_address = $1 LIMIT 1`,
|
|
24
|
+
[email]
|
|
25
|
+
);
|
|
26
|
+
if (!userResult.rows.length) {
|
|
27
|
+
return createErrorResponse(403, 'User not found');
|
|
28
|
+
}
|
|
29
|
+
if (userResult.rows[0].subscription_tier !== 'enterprise') {
|
|
30
|
+
return createErrorResponse(403, 'Enterprise subscription required');
|
|
31
|
+
}
|
|
32
|
+
const clientId = userResult.rows[0].client_id;
|
|
33
|
+
|
|
34
|
+
const params = queryStringParameters || {};
|
|
35
|
+
const period = params.period || '30d';
|
|
36
|
+
|
|
37
|
+
// Calculate date filter
|
|
38
|
+
let intervalDays;
|
|
39
|
+
switch (period) {
|
|
40
|
+
case '7d': intervalDays = 7; break;
|
|
41
|
+
case '90d': intervalDays = 90; break;
|
|
42
|
+
case '30d':
|
|
43
|
+
default: intervalDays = 30; break;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Aggregate contributions across audit trail and knowledge curation
|
|
47
|
+
const result = await executeQuery(
|
|
48
|
+
`WITH audit_activity AS (
|
|
49
|
+
SELECT
|
|
50
|
+
actor_email as contributor_email,
|
|
51
|
+
COUNT(*) FILTER (WHERE event_type = 'DATA_CREATE' AND entity_type = 'STANDARD') as discoveries,
|
|
52
|
+
COUNT(*) FILTER (WHERE event_type = 'DATA_UPDATE' AND entity_type = 'STANDARD') as refinements,
|
|
53
|
+
COUNT(*) FILTER (WHERE event_type = 'DATA_UPDATE' AND entity_type = 'STANDARD'
|
|
54
|
+
AND (after_state->>'status') = 'approved') as verifications,
|
|
55
|
+
COUNT(*) FILTER (WHERE event_type = 'DATA_CREATE' AND entity_type = 'KNOWLEDGE') as curations
|
|
56
|
+
FROM rapport.unified_audit_log
|
|
57
|
+
WHERE client_id = $1
|
|
58
|
+
AND created_at >= NOW() - ($2 || ' days')::interval
|
|
59
|
+
AND actor_email IS NOT NULL
|
|
60
|
+
GROUP BY actor_email
|
|
61
|
+
)
|
|
62
|
+
SELECT
|
|
63
|
+
contributor_email,
|
|
64
|
+
COALESCE(discoveries, 0) as discoveries,
|
|
65
|
+
COALESCE(refinements, 0) as refinements,
|
|
66
|
+
COALESCE(verifications, 0) as verifications,
|
|
67
|
+
COALESCE(curations, 0) as curations,
|
|
68
|
+
COALESCE(discoveries * 3 + refinements * 2 + verifications * 2 + curations * 4, 0) as total_weight
|
|
69
|
+
FROM audit_activity
|
|
70
|
+
ORDER BY total_weight DESC`,
|
|
71
|
+
[clientId, String(intervalDays)]
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return createSuccessResponse(
|
|
75
|
+
{ Records: result.rows },
|
|
76
|
+
'Contributors retrieved',
|
|
77
|
+
{ Total_Records: result.rowCount, Request_ID, Timestamp: new Date().toISOString() }
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
} catch (error) {
|
|
81
|
+
return handleError(error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
exports.handler = wrapHandler(getEnterpriseContributors);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enterprise Knowledge Categories Get Handler
|
|
3
|
+
*
|
|
4
|
+
* GET /api/enterprise/knowledge/categories
|
|
5
|
+
* Returns: { Records: [{ category_id, name, description }] } + meta
|
|
6
|
+
* Auth: Cognito JWT required, enterprise tier only
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
|
|
10
|
+
|
|
11
|
+
async function getEnterpriseKnowledgeCategories({ requestContext }) {
|
|
12
|
+
try {
|
|
13
|
+
const Request_ID = requestContext.requestId;
|
|
14
|
+
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
15
|
+
if (!email) {
|
|
16
|
+
return createErrorResponse(401, 'Authentication required');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const userResult = await executeQuery(
|
|
20
|
+
`SELECT u.client_id, c.subscription_tier
|
|
21
|
+
FROM rapport.users u
|
|
22
|
+
JOIN rapport.clients c ON c.client_id = u.client_id
|
|
23
|
+
WHERE u.email_address = $1 LIMIT 1`,
|
|
24
|
+
[email]
|
|
25
|
+
);
|
|
26
|
+
if (!userResult.rows.length) {
|
|
27
|
+
return createErrorResponse(403, 'User not found');
|
|
28
|
+
}
|
|
29
|
+
if (userResult.rows[0].subscription_tier !== 'enterprise') {
|
|
30
|
+
return createErrorResponse(403, 'Enterprise subscription required');
|
|
31
|
+
}
|
|
32
|
+
const clientId = userResult.rows[0].client_id;
|
|
33
|
+
|
|
34
|
+
const result = await executeQuery(
|
|
35
|
+
`SELECT category_id, name, description
|
|
36
|
+
FROM rapport.knowledge_categories
|
|
37
|
+
WHERE client_id = $1
|
|
38
|
+
ORDER BY name`,
|
|
39
|
+
[clientId]
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return createSuccessResponse(
|
|
43
|
+
{ Records: result.rows },
|
|
44
|
+
'Categories retrieved',
|
|
45
|
+
{ Total_Records: result.rowCount, Request_ID, Timestamp: new Date().toISOString() }
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
} catch (error) {
|
|
49
|
+
return handleError(error);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
exports.handler = wrapHandler(getEnterpriseKnowledgeCategories);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enterprise Knowledge Create Handler
|
|
3
|
+
*
|
|
4
|
+
* POST /api/enterprise/knowledge
|
|
5
|
+
* Body: { title, content, category?, tags?, invariant_id? }
|
|
6
|
+
* Returns: { Records: [CuratedKnowledge] } + meta
|
|
7
|
+
* Auth: Cognito JWT required, enterprise tier only
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError, logModificationEvent, AuditEventType, EntityType } = require('./helpers');
|
|
11
|
+
|
|
12
|
+
async function createEnterpriseKnowledge({ body, requestContext }) {
|
|
13
|
+
try {
|
|
14
|
+
const Request_ID = requestContext.requestId;
|
|
15
|
+
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
16
|
+
if (!email) {
|
|
17
|
+
return createErrorResponse(401, 'Authentication required');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const userResult = await executeQuery(
|
|
21
|
+
`SELECT u.client_id, c.subscription_tier
|
|
22
|
+
FROM rapport.users u
|
|
23
|
+
JOIN rapport.clients c ON c.client_id = u.client_id
|
|
24
|
+
WHERE u.email_address = $1 LIMIT 1`,
|
|
25
|
+
[email]
|
|
26
|
+
);
|
|
27
|
+
if (!userResult.rows.length) {
|
|
28
|
+
return createErrorResponse(403, 'User not found');
|
|
29
|
+
}
|
|
30
|
+
if (userResult.rows[0].subscription_tier !== 'enterprise') {
|
|
31
|
+
return createErrorResponse(403, 'Enterprise subscription required');
|
|
32
|
+
}
|
|
33
|
+
const clientId = userResult.rows[0].client_id;
|
|
34
|
+
|
|
35
|
+
const requestBody = body || {};
|
|
36
|
+
const { title, content, category, tags, invariant_id } = requestBody;
|
|
37
|
+
|
|
38
|
+
if (!title || !content) {
|
|
39
|
+
return createErrorResponse(400, 'title and content are required');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const result = await executeQuery(
|
|
43
|
+
`INSERT INTO rapport.curated_knowledge
|
|
44
|
+
(client_id, invariant_id, title, content, category, tags, curator_email, status)
|
|
45
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, 'draft')
|
|
46
|
+
RETURNING *`,
|
|
47
|
+
[
|
|
48
|
+
clientId,
|
|
49
|
+
invariant_id || null,
|
|
50
|
+
title,
|
|
51
|
+
content,
|
|
52
|
+
category || null,
|
|
53
|
+
tags || [],
|
|
54
|
+
email,
|
|
55
|
+
]
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const created = result.rows[0];
|
|
59
|
+
|
|
60
|
+
await logModificationEvent(AuditEventType.DATA_CREATE, EntityType.KNOWLEDGE, String(created.knowledge_id), {
|
|
61
|
+
requestContext,
|
|
62
|
+
client_id: clientId,
|
|
63
|
+
after_state: { title, category, status: 'draft' },
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return createSuccessResponse(
|
|
67
|
+
{ Records: [created] },
|
|
68
|
+
'Knowledge item created',
|
|
69
|
+
{ Total_Records: 1, Request_ID, Timestamp: new Date().toISOString() }
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
} catch (error) {
|
|
73
|
+
return handleError(error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
exports.handler = wrapHandler(createEnterpriseKnowledge);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enterprise Knowledge Delete Handler
|
|
3
|
+
*
|
|
4
|
+
* DELETE /api/enterprise/knowledge?knowledge_id=xxx
|
|
5
|
+
* Returns: { Records: [] } + meta
|
|
6
|
+
* Auth: Cognito JWT required, enterprise tier only
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError, logModificationEvent, AuditEventType, EntityType } = require('./helpers');
|
|
10
|
+
|
|
11
|
+
async function deleteEnterpriseKnowledge({ queryStringParameters, requestContext }) {
|
|
12
|
+
try {
|
|
13
|
+
const Request_ID = requestContext.requestId;
|
|
14
|
+
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
15
|
+
if (!email) {
|
|
16
|
+
return createErrorResponse(401, 'Authentication required');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const userResult = await executeQuery(
|
|
20
|
+
`SELECT u.client_id, c.subscription_tier
|
|
21
|
+
FROM rapport.users u
|
|
22
|
+
JOIN rapport.clients c ON c.client_id = u.client_id
|
|
23
|
+
WHERE u.email_address = $1 LIMIT 1`,
|
|
24
|
+
[email]
|
|
25
|
+
);
|
|
26
|
+
if (!userResult.rows.length) {
|
|
27
|
+
return createErrorResponse(403, 'User not found');
|
|
28
|
+
}
|
|
29
|
+
if (userResult.rows[0].subscription_tier !== 'enterprise') {
|
|
30
|
+
return createErrorResponse(403, 'Enterprise subscription required');
|
|
31
|
+
}
|
|
32
|
+
const clientId = userResult.rows[0].client_id;
|
|
33
|
+
|
|
34
|
+
const knowledgeId = (queryStringParameters || {}).knowledge_id;
|
|
35
|
+
if (!knowledgeId) {
|
|
36
|
+
return createErrorResponse(400, 'knowledge_id is required');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Fetch current state for audit trail
|
|
40
|
+
const current = await executeQuery(
|
|
41
|
+
'SELECT * FROM rapport.curated_knowledge WHERE knowledge_id = $1 AND client_id = $2',
|
|
42
|
+
[knowledgeId, clientId]
|
|
43
|
+
);
|
|
44
|
+
if (!current.rows.length) {
|
|
45
|
+
return createErrorResponse(404, 'Knowledge item not found');
|
|
46
|
+
}
|
|
47
|
+
const beforeState = current.rows[0];
|
|
48
|
+
|
|
49
|
+
await executeQuery(
|
|
50
|
+
'DELETE FROM rapport.curated_knowledge WHERE knowledge_id = $1 AND client_id = $2',
|
|
51
|
+
[knowledgeId, clientId]
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
await logModificationEvent(AuditEventType.DATA_DELETE, EntityType.KNOWLEDGE, String(knowledgeId), {
|
|
55
|
+
requestContext,
|
|
56
|
+
client_id: clientId,
|
|
57
|
+
before_state: { title: beforeState.title, status: beforeState.status },
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return createSuccessResponse(
|
|
61
|
+
{ Records: [] },
|
|
62
|
+
'Knowledge item deleted',
|
|
63
|
+
{ Total_Records: 0, Request_ID, Timestamp: new Date().toISOString() }
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return handleError(error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
exports.handler = wrapHandler(deleteEnterpriseKnowledge);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enterprise Knowledge Get Handler
|
|
3
|
+
*
|
|
4
|
+
* GET /api/enterprise/knowledge?page=&pageSize=&status=&category=
|
|
5
|
+
* Returns: { Records: CuratedKnowledge[] } + meta { Total_Records, Page, Page_Size, Request_ID, Timestamp }
|
|
6
|
+
* Auth: Cognito JWT required, enterprise tier only
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { wrapHandler, executeQuery, createSuccessResponse, createErrorResponse, handleError } = require('./helpers');
|
|
10
|
+
|
|
11
|
+
async function getEnterpriseKnowledge({ queryStringParameters, requestContext }) {
|
|
12
|
+
try {
|
|
13
|
+
const Request_ID = requestContext.requestId;
|
|
14
|
+
const email = requestContext.authorizer?.claims?.email || requestContext.authorizer?.jwt?.claims?.email;
|
|
15
|
+
if (!email) {
|
|
16
|
+
return createErrorResponse(401, 'Authentication required');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const userResult = await executeQuery(
|
|
20
|
+
`SELECT u.client_id, c.subscription_tier
|
|
21
|
+
FROM rapport.users u
|
|
22
|
+
JOIN rapport.clients c ON c.client_id = u.client_id
|
|
23
|
+
WHERE u.email_address = $1 LIMIT 1`,
|
|
24
|
+
[email]
|
|
25
|
+
);
|
|
26
|
+
if (!userResult.rows.length) {
|
|
27
|
+
return createErrorResponse(403, 'User not found');
|
|
28
|
+
}
|
|
29
|
+
if (userResult.rows[0].subscription_tier !== 'enterprise') {
|
|
30
|
+
return createErrorResponse(403, 'Enterprise subscription required');
|
|
31
|
+
}
|
|
32
|
+
const clientId = userResult.rows[0].client_id;
|
|
33
|
+
|
|
34
|
+
const params = queryStringParameters || {};
|
|
35
|
+
const page = Math.max(1, parseInt(params.page) || 1);
|
|
36
|
+
const pageSize = Math.min(100, Math.max(1, parseInt(params.pageSize) || 20));
|
|
37
|
+
const offset = (page - 1) * pageSize;
|
|
38
|
+
|
|
39
|
+
const conditions = ['client_id = $1'];
|
|
40
|
+
const values = [clientId];
|
|
41
|
+
let paramIndex = 2;
|
|
42
|
+
|
|
43
|
+
if (params.status) {
|
|
44
|
+
conditions.push(`status = $${paramIndex}`);
|
|
45
|
+
values.push(params.status);
|
|
46
|
+
paramIndex++;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (params.category) {
|
|
50
|
+
conditions.push(`category = $${paramIndex}`);
|
|
51
|
+
values.push(params.category);
|
|
52
|
+
paramIndex++;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const whereClause = conditions.join(' AND ');
|
|
56
|
+
|
|
57
|
+
const countResult = await executeQuery(
|
|
58
|
+
`SELECT COUNT(*) as total FROM rapport.curated_knowledge WHERE ${whereClause}`,
|
|
59
|
+
values
|
|
60
|
+
);
|
|
61
|
+
const total = parseInt(countResult.rows[0].total);
|
|
62
|
+
|
|
63
|
+
const dataResult = await executeQuery(
|
|
64
|
+
`SELECT
|
|
65
|
+
knowledge_id, client_id, invariant_id,
|
|
66
|
+
title, content, category, tags,
|
|
67
|
+
curator_email, status,
|
|
68
|
+
created_at, published_at
|
|
69
|
+
FROM rapport.curated_knowledge
|
|
70
|
+
WHERE ${whereClause}
|
|
71
|
+
ORDER BY created_at DESC
|
|
72
|
+
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
|
|
73
|
+
[...values, pageSize, offset]
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return createSuccessResponse(
|
|
77
|
+
{ Records: dataResult.rows },
|
|
78
|
+
'Knowledge items retrieved',
|
|
79
|
+
{ Total_Records: total, Page: page, Page_Size: pageSize, Request_ID, Timestamp: new Date().toISOString() }
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return handleError(error);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
exports.handler = wrapHandler(getEnterpriseKnowledge);
|