@benchcubed/today-cli 1.0.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 +296 -0
- package/dist/ai/bedrock-client.d.ts +1 -0
- package/dist/ai/bedrock-client.d.ts.map +1 -0
- package/dist/ai/bedrock-client.js +2 -0
- package/dist/ai/bedrock-client.js.map +1 -0
- package/dist/ai/knowledge-processor.d.ts +1 -0
- package/dist/ai/knowledge-processor.d.ts.map +1 -0
- package/dist/ai/knowledge-processor.js +2 -0
- package/dist/ai/knowledge-processor.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +70 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/ask.d.ts +3 -0
- package/dist/commands/ask.d.ts.map +1 -0
- package/dist/commands/ask.js +217 -0
- package/dist/commands/ask.js.map +1 -0
- package/dist/commands/audit.d.ts +3 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +153 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +114 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +72 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/embeddings.d.ts +3 -0
- package/dist/commands/embeddings.d.ts.map +1 -0
- package/dist/commands/embeddings.js +118 -0
- package/dist/commands/embeddings.js.map +1 -0
- package/dist/commands/experts.d.ts +3 -0
- package/dist/commands/experts.d.ts.map +1 -0
- package/dist/commands/experts.js +129 -0
- package/dist/commands/experts.js.map +1 -0
- package/dist/commands/export-import.d.ts +4 -0
- package/dist/commands/export-import.d.ts.map +1 -0
- package/dist/commands/export-import.js +285 -0
- package/dist/commands/export-import.js.map +1 -0
- package/dist/commands/git.d.ts +4 -0
- package/dist/commands/git.d.ts.map +1 -0
- package/dist/commands/git.js +274 -0
- package/dist/commands/git.js.map +1 -0
- package/dist/commands/impact.d.ts +3 -0
- package/dist/commands/impact.d.ts.map +1 -0
- package/dist/commands/impact.js +121 -0
- package/dist/commands/impact.js.map +1 -0
- package/dist/commands/insights.d.ts +3 -0
- package/dist/commands/insights.d.ts.map +1 -0
- package/dist/commands/insights.js +113 -0
- package/dist/commands/insights.js.map +1 -0
- package/dist/commands/learned.d.ts +3 -0
- package/dist/commands/learned.d.ts.map +1 -0
- package/dist/commands/learned.js +355 -0
- package/dist/commands/learned.js.map +1 -0
- package/dist/commands/onboard.d.ts +3 -0
- package/dist/commands/onboard.d.ts.map +1 -0
- package/dist/commands/onboard.js +141 -0
- package/dist/commands/onboard.js.map +1 -0
- package/dist/commands/recent.d.ts +3 -0
- package/dist/commands/recent.d.ts.map +1 -0
- package/dist/commands/recent.js +83 -0
- package/dist/commands/recent.js.map +1 -0
- package/dist/commands/secrets.d.ts +3 -0
- package/dist/commands/secrets.d.ts.map +1 -0
- package/dist/commands/secrets.js +203 -0
- package/dist/commands/secrets.js.map +1 -0
- package/dist/commands/stats.d.ts +3 -0
- package/dist/commands/stats.d.ts.map +1 -0
- package/dist/commands/stats.js +120 -0
- package/dist/commands/stats.js.map +1 -0
- package/dist/config/index.d.ts +12 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +46 -0
- package/dist/config/index.js.map +1 -0
- package/dist/database/migrations.d.ts +13 -0
- package/dist/database/migrations.d.ts.map +1 -0
- package/dist/database/migrations.js +190 -0
- package/dist/database/migrations.js.map +1 -0
- package/dist/database/postgresql.d.ts +92 -0
- package/dist/database/postgresql.d.ts.map +1 -0
- package/dist/database/postgresql.js +382 -0
- package/dist/database/postgresql.js.map +1 -0
- package/dist/database/sqlite.d.ts +54 -0
- package/dist/database/sqlite.d.ts.map +1 -0
- package/dist/database/sqlite.js +337 -0
- package/dist/database/sqlite.js.map +1 -0
- package/dist/lambda/admin.d.ts +3 -0
- package/dist/lambda/admin.d.ts.map +1 -0
- package/dist/lambda/admin.js +818 -0
- package/dist/lambda/admin.js.map +1 -0
- package/dist/lambda/ai-services.d.ts +6 -0
- package/dist/lambda/ai-services.d.ts.map +1 -0
- package/dist/lambda/ai-services.js +472 -0
- package/dist/lambda/ai-services.js.map +1 -0
- package/dist/lambda/analytics.d.ts +3 -0
- package/dist/lambda/analytics.d.ts.map +1 -0
- package/dist/lambda/analytics.js +481 -0
- package/dist/lambda/analytics.js.map +1 -0
- package/dist/lambda/api-router.d.ts +3 -0
- package/dist/lambda/api-router.d.ts.map +1 -0
- package/dist/lambda/api-router.js +162 -0
- package/dist/lambda/api-router.js.map +1 -0
- package/dist/lambda/custom-topics.d.ts +3 -0
- package/dist/lambda/custom-topics.d.ts.map +1 -0
- package/dist/lambda/custom-topics.js +425 -0
- package/dist/lambda/custom-topics.js.map +1 -0
- package/dist/lambda/graph-builder.d.ts +3 -0
- package/dist/lambda/graph-builder.d.ts.map +1 -0
- package/dist/lambda/graph-builder.js +442 -0
- package/dist/lambda/graph-builder.js.map +1 -0
- package/dist/lambda/knowledge-ai.d.ts +3 -0
- package/dist/lambda/knowledge-ai.d.ts.map +1 -0
- package/dist/lambda/knowledge-ai.js +849 -0
- package/dist/lambda/knowledge-ai.js.map +1 -0
- package/dist/lambda/post-confirmation.d.ts +8 -0
- package/dist/lambda/post-confirmation.d.ts.map +1 -0
- package/dist/lambda/post-confirmation.js +92 -0
- package/dist/lambda/post-confirmation.js.map +1 -0
- package/dist/lambda/teams.d.ts +3 -0
- package/dist/lambda/teams.d.ts.map +1 -0
- package/dist/lambda/teams.js +568 -0
- package/dist/lambda/teams.js.map +1 -0
- package/dist/lib/export-helpers.d.ts +5 -0
- package/dist/lib/export-helpers.d.ts.map +1 -0
- package/dist/lib/export-helpers.js +137 -0
- package/dist/lib/export-helpers.js.map +1 -0
- package/dist/lib/import-helpers.d.ts +5 -0
- package/dist/lib/import-helpers.d.ts.map +1 -0
- package/dist/lib/import-helpers.js +185 -0
- package/dist/lib/import-helpers.js.map +1 -0
- package/dist/lib/insights-helpers.d.ts +123 -0
- package/dist/lib/insights-helpers.d.ts.map +1 -0
- package/dist/lib/insights-helpers.js +374 -0
- package/dist/lib/insights-helpers.js.map +1 -0
- package/dist/lib/search-helpers.d.ts +4 -0
- package/dist/lib/search-helpers.d.ts.map +1 -0
- package/dist/lib/search-helpers.js +124 -0
- package/dist/lib/search-helpers.js.map +1 -0
- package/dist/lib/secret-helpers.d.ts +2 -0
- package/dist/lib/secret-helpers.d.ts.map +1 -0
- package/dist/lib/secret-helpers.js +85 -0
- package/dist/lib/secret-helpers.js.map +1 -0
- package/dist/lib/stats-helpers.d.ts +41 -0
- package/dist/lib/stats-helpers.d.ts.map +1 -0
- package/dist/lib/stats-helpers.js +263 -0
- package/dist/lib/stats-helpers.js.map +1 -0
- package/dist/services/aws-api.d.ts +81 -0
- package/dist/services/aws-api.d.ts.map +1 -0
- package/dist/services/aws-api.js +388 -0
- package/dist/services/aws-api.js.map +1 -0
- package/dist/services/bedrock.d.ts +83 -0
- package/dist/services/bedrock.d.ts.map +1 -0
- package/dist/services/bedrock.js +434 -0
- package/dist/services/bedrock.js.map +1 -0
- package/dist/services/commit-learnings.d.ts +25 -0
- package/dist/services/commit-learnings.d.ts.map +1 -0
- package/dist/services/commit-learnings.js +180 -0
- package/dist/services/commit-learnings.js.map +1 -0
- package/dist/services/embedding-storage.d.ts +42 -0
- package/dist/services/embedding-storage.d.ts.map +1 -0
- package/dist/services/embedding-storage.js +124 -0
- package/dist/services/embedding-storage.js.map +1 -0
- package/dist/services/expert-engine.d.ts +21 -0
- package/dist/services/expert-engine.d.ts.map +1 -0
- package/dist/services/expert-engine.js +58 -0
- package/dist/services/expert-engine.js.map +1 -0
- package/dist/services/onboarding-accelerator.d.ts +118 -0
- package/dist/services/onboarding-accelerator.d.ts.map +1 -0
- package/dist/services/onboarding-accelerator.js +403 -0
- package/dist/services/onboarding-accelerator.js.map +1 -0
- package/dist/services/secret-detection.d.ts +46 -0
- package/dist/services/secret-detection.d.ts.map +1 -0
- package/dist/services/secret-detection.js +75 -0
- package/dist/services/secret-detection.js.map +1 -0
- package/dist/services/secret-manager-simple.d.ts +51 -0
- package/dist/services/secret-manager-simple.d.ts.map +1 -0
- package/dist/services/secret-manager-simple.js +119 -0
- package/dist/services/secret-manager-simple.js.map +1 -0
- package/dist/services/secret-manager.d.ts +150 -0
- package/dist/services/secret-manager.d.ts.map +1 -0
- package/dist/services/secret-manager.js +287 -0
- package/dist/services/secret-manager.js.map +1 -0
- package/dist/services/vector-embeddings.d.ts +70 -0
- package/dist/services/vector-embeddings.d.ts.map +1 -0
- package/dist/services/vector-embeddings.js +167 -0
- package/dist/services/vector-embeddings.js.map +1 -0
- package/dist/services/vector-search.d.ts +28 -0
- package/dist/services/vector-search.d.ts.map +1 -0
- package/dist/services/vector-search.js +192 -0
- package/dist/services/vector-search.js.map +1 -0
- package/dist/types/index.d.ts +86 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/formatting.d.ts +10 -0
- package/dist/utils/formatting.d.ts.map +1 -0
- package/dist/utils/formatting.js +82 -0
- package/dist/utils/formatting.js.map +1 -0
- package/dist/utils/git.d.ts +52 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +182 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/validation.d.ts +17 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +187 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handler = void 0;
|
|
4
|
+
const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
|
|
5
|
+
const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
|
|
6
|
+
const client_cognito_identity_provider_1 = require("@aws-sdk/client-cognito-identity-provider");
|
|
7
|
+
const dynamoClient = new client_dynamodb_1.DynamoDBClient({ region: process.env.AWS_REGION || 'eu-west-2' });
|
|
8
|
+
const docClient = lib_dynamodb_1.DynamoDBDocumentClient.from(dynamoClient);
|
|
9
|
+
const cognitoClient = new client_cognito_identity_provider_1.CognitoIdentityProviderClient({ region: process.env.AWS_REGION || 'eu-west-2' });
|
|
10
|
+
const KNOWLEDGE_TABLE = process.env.KNOWLEDGE_TABLE_NAME;
|
|
11
|
+
const USER_POOL_ID = process.env.USER_POOL_ID;
|
|
12
|
+
const CLIENT_ID = process.env.CLIENT_ID;
|
|
13
|
+
const corsHeaders = {
|
|
14
|
+
'Access-Control-Allow-Origin': '*',
|
|
15
|
+
'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
|
|
16
|
+
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
|
|
17
|
+
'Content-Type': 'application/json'
|
|
18
|
+
};
|
|
19
|
+
const handler = async (event) => {
|
|
20
|
+
console.log('Admin API Event:', JSON.stringify(event, null, 2));
|
|
21
|
+
// Handle CORS preflight
|
|
22
|
+
if (event.httpMethod === 'OPTIONS') {
|
|
23
|
+
return {
|
|
24
|
+
statusCode: 200,
|
|
25
|
+
headers: corsHeaders,
|
|
26
|
+
body: ''
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const path = event.path;
|
|
31
|
+
const method = event.httpMethod;
|
|
32
|
+
const pathParams = event.pathParameters || {};
|
|
33
|
+
const queryParams = event.queryStringParameters || {};
|
|
34
|
+
const body = event.body ? JSON.parse(event.body) : {};
|
|
35
|
+
console.log('Request details:', {
|
|
36
|
+
path,
|
|
37
|
+
method,
|
|
38
|
+
pathParams,
|
|
39
|
+
resource: event.resource,
|
|
40
|
+
headers: event.headers
|
|
41
|
+
});
|
|
42
|
+
// Handle proxy path - extract the actual route from the proxy parameter
|
|
43
|
+
// For proxy routes: pathParams.proxy contains the path after /admin/
|
|
44
|
+
// For non-proxy routes like /api/admin/auth: extract from full path
|
|
45
|
+
let routePath;
|
|
46
|
+
if (pathParams.proxy) {
|
|
47
|
+
const proxyPath = pathParams.proxy;
|
|
48
|
+
// Handle routes like "admin/auth" -> "/auth" or just "auth" -> "/auth"
|
|
49
|
+
const pathParts = proxyPath.split('/');
|
|
50
|
+
const routePart = pathParts[pathParts.length - 1]; // Get last part
|
|
51
|
+
routePath = `/${routePart}`;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// Extract everything after /api/admin
|
|
55
|
+
const match = path.match(/\/api\/admin(\/.*)?$/);
|
|
56
|
+
routePath = match ? (match[1] || '') : '';
|
|
57
|
+
}
|
|
58
|
+
console.log('Resolved route path:', routePath);
|
|
59
|
+
// Route handling - simplified routing based on the proxy path
|
|
60
|
+
if (routePath === '/auth' && method === 'POST') {
|
|
61
|
+
return await handleAuth(body);
|
|
62
|
+
}
|
|
63
|
+
if (routePath === '/stats' && method === 'GET') {
|
|
64
|
+
return await getAdminStats();
|
|
65
|
+
}
|
|
66
|
+
if (routePath === '/knowledge' && method === 'GET') {
|
|
67
|
+
return await getKnowledgeEntries(queryParams);
|
|
68
|
+
}
|
|
69
|
+
if (routePath === '/knowledge' && method === 'POST') {
|
|
70
|
+
return await createKnowledgeEntry(body);
|
|
71
|
+
}
|
|
72
|
+
if (routePath.startsWith('/knowledge/') && method === 'PUT') {
|
|
73
|
+
const entryId = routePath.split('/')[2];
|
|
74
|
+
return await updateKnowledgeEntry(entryId, body);
|
|
75
|
+
}
|
|
76
|
+
if (routePath.startsWith('/knowledge/') && routePath.endsWith('/approve') && method === 'POST') {
|
|
77
|
+
const entryId = routePath.split('/')[2];
|
|
78
|
+
return await approveKnowledgeEntry(entryId);
|
|
79
|
+
}
|
|
80
|
+
if (routePath.startsWith('/knowledge/') && routePath.endsWith('/reject') && method === 'POST') {
|
|
81
|
+
const entryId = routePath.split('/')[2];
|
|
82
|
+
return await rejectKnowledgeEntry(entryId, body.reason);
|
|
83
|
+
}
|
|
84
|
+
if (routePath.startsWith('/knowledge/') && routePath.endsWith('/assign-expert') && method === 'POST') {
|
|
85
|
+
const entryId = routePath.split('/')[2];
|
|
86
|
+
return await assignExpertToEntry(entryId, body.expertId);
|
|
87
|
+
}
|
|
88
|
+
if (routePath === '/knowledge/bulk-approve' && method === 'POST') {
|
|
89
|
+
return await bulkApproveEntries(body.entryIds);
|
|
90
|
+
}
|
|
91
|
+
if (routePath === '/experts' && method === 'GET') {
|
|
92
|
+
return await getExperts();
|
|
93
|
+
}
|
|
94
|
+
if (routePath === '/experts' && method === 'POST') {
|
|
95
|
+
return await createExpert(body);
|
|
96
|
+
}
|
|
97
|
+
if (routePath.startsWith('/experts/') && routePath.endsWith('/toggle-status') && method === 'POST') {
|
|
98
|
+
const expertId = routePath.split('/')[2];
|
|
99
|
+
return await toggleExpertStatus(expertId);
|
|
100
|
+
}
|
|
101
|
+
if (routePath.startsWith('/experts/') && routePath.endsWith('/availability') && method === 'POST') {
|
|
102
|
+
const expertId = routePath.split('/')[2];
|
|
103
|
+
return await updateExpertAvailability(expertId, body.availability);
|
|
104
|
+
}
|
|
105
|
+
if (routePath === '/topics' && method === 'GET') {
|
|
106
|
+
return await getTopics();
|
|
107
|
+
}
|
|
108
|
+
if (routePath === '/search' && method === 'GET') {
|
|
109
|
+
return await searchKnowledge(queryParams.q || '');
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
statusCode: 404,
|
|
113
|
+
headers: corsHeaders,
|
|
114
|
+
body: JSON.stringify({ error: 'Endpoint not found' })
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
console.error('Admin API Error:', error);
|
|
119
|
+
return {
|
|
120
|
+
statusCode: 500,
|
|
121
|
+
headers: corsHeaders,
|
|
122
|
+
body: JSON.stringify({
|
|
123
|
+
error: 'Internal server error',
|
|
124
|
+
message: error instanceof Error ? error.message : 'Unknown error'
|
|
125
|
+
})
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
exports.handler = handler;
|
|
130
|
+
async function handleAuth(body) {
|
|
131
|
+
try {
|
|
132
|
+
const { username, password } = body;
|
|
133
|
+
if (!username || !password) {
|
|
134
|
+
return {
|
|
135
|
+
statusCode: 400,
|
|
136
|
+
headers: corsHeaders,
|
|
137
|
+
body: JSON.stringify({ error: 'Username and password are required' })
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// Use AdminInitiateAuth for server-side authentication
|
|
141
|
+
const command = new client_cognito_identity_provider_1.AdminInitiateAuthCommand({
|
|
142
|
+
UserPoolId: USER_POOL_ID,
|
|
143
|
+
ClientId: CLIENT_ID,
|
|
144
|
+
AuthFlow: 'ADMIN_NO_SRP_AUTH',
|
|
145
|
+
AuthParameters: {
|
|
146
|
+
USERNAME: username,
|
|
147
|
+
PASSWORD: password
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
const response = await cognitoClient.send(command);
|
|
151
|
+
if (!response.AuthenticationResult) {
|
|
152
|
+
return {
|
|
153
|
+
statusCode: 401,
|
|
154
|
+
headers: corsHeaders,
|
|
155
|
+
body: JSON.stringify({ error: 'Authentication failed' })
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
statusCode: 200,
|
|
160
|
+
headers: corsHeaders,
|
|
161
|
+
body: JSON.stringify({
|
|
162
|
+
token: response.AuthenticationResult.IdToken,
|
|
163
|
+
accessToken: response.AuthenticationResult.AccessToken,
|
|
164
|
+
refreshToken: response.AuthenticationResult.RefreshToken,
|
|
165
|
+
expiresIn: response.AuthenticationResult.ExpiresIn
|
|
166
|
+
})
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
console.error('Auth error:', error);
|
|
171
|
+
return {
|
|
172
|
+
statusCode: 401,
|
|
173
|
+
headers: corsHeaders,
|
|
174
|
+
body: JSON.stringify({
|
|
175
|
+
error: 'Authentication failed',
|
|
176
|
+
message: error.message || 'Invalid credentials'
|
|
177
|
+
})
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function getAdminStats() {
|
|
182
|
+
try {
|
|
183
|
+
// Get all knowledge entries from DynamoDB
|
|
184
|
+
const entriesResult = await docClient.send(new lib_dynamodb_1.ScanCommand({
|
|
185
|
+
TableName: KNOWLEDGE_TABLE
|
|
186
|
+
}));
|
|
187
|
+
console.log(`Found ${entriesResult.Items?.length || 0} raw items in DynamoDB`);
|
|
188
|
+
// Parse entries from the stored JSON format
|
|
189
|
+
const entries = [];
|
|
190
|
+
(entriesResult.Items || []).forEach(item => {
|
|
191
|
+
if (item.entry_data) {
|
|
192
|
+
try {
|
|
193
|
+
// Document client automatically unmarshals DynamoDB types, so entry_data is already a string
|
|
194
|
+
const entryData = JSON.parse(item.entry_data);
|
|
195
|
+
entries.push({
|
|
196
|
+
...entryData,
|
|
197
|
+
status: entryData.status || 'approved', // Default to approved for existing entries
|
|
198
|
+
assignedExperts: entryData.assignedExperts || []
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
console.warn('Failed to parse entry data:', error, 'Raw item:', JSON.stringify(item));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
console.log(`Successfully parsed ${entries.length} knowledge entries`);
|
|
207
|
+
// Extract experts from the actual knowledge entries
|
|
208
|
+
const expertMap = new Map();
|
|
209
|
+
entries.forEach(entry => {
|
|
210
|
+
if (entry.authorId) {
|
|
211
|
+
// Handle missing authorName by converting authorId to readable name
|
|
212
|
+
const authorName = entry.authorName || entry.authorId.replace(/-/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase());
|
|
213
|
+
if (!expertMap.has(entry.authorId)) {
|
|
214
|
+
expertMap.set(entry.authorId, {
|
|
215
|
+
id: entry.authorId,
|
|
216
|
+
name: authorName,
|
|
217
|
+
email: `${entry.authorId}@company.com`,
|
|
218
|
+
expertise: [],
|
|
219
|
+
entryCount: 0,
|
|
220
|
+
helpfulnessScore: 0,
|
|
221
|
+
availability: 'medium',
|
|
222
|
+
isActive: true,
|
|
223
|
+
createdAt: entry.createdAt,
|
|
224
|
+
lastActive: entry.createdAt
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
const expert = expertMap.get(entry.authorId);
|
|
228
|
+
expert.entryCount++;
|
|
229
|
+
expert.helpfulnessScore += entry.helpfulnessRating || 0;
|
|
230
|
+
// Add topics to expertise
|
|
231
|
+
if (entry.aiTopics && Array.isArray(entry.aiTopics)) {
|
|
232
|
+
entry.aiTopics.forEach(topic => {
|
|
233
|
+
if (!expert.expertise.includes(topic)) {
|
|
234
|
+
expert.expertise.push(topic);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
// Update last active if this entry is newer
|
|
239
|
+
if (entry.createdAt > expert.lastActive) {
|
|
240
|
+
expert.lastActive = entry.createdAt;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
// Calculate average helpfulness scores
|
|
245
|
+
const experts = Array.from(expertMap.values()).map(expert => ({
|
|
246
|
+
...expert,
|
|
247
|
+
helpfulnessScore: expert.entryCount > 0 ? expert.helpfulnessScore / expert.entryCount : 0
|
|
248
|
+
}));
|
|
249
|
+
console.log(`Extracted ${experts.length} experts from entries`);
|
|
250
|
+
// Calculate statistics
|
|
251
|
+
const totalEntries = entries.length;
|
|
252
|
+
const pendingApprovals = entries.filter(e => e.status === 'pending').length;
|
|
253
|
+
const totalExperts = experts.length;
|
|
254
|
+
const activeExperts = experts.filter(e => e.isActive).length;
|
|
255
|
+
// Calculate recent activity (last 7 days)
|
|
256
|
+
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
257
|
+
const recentEntries = entries.filter(e => e.createdAt && e.createdAt > sevenDaysAgo);
|
|
258
|
+
const recentExperts = experts.filter(e => e.createdAt && e.createdAt > sevenDaysAgo);
|
|
259
|
+
// Calculate top topics
|
|
260
|
+
const topicCounts = new Map();
|
|
261
|
+
entries.forEach(entry => {
|
|
262
|
+
// Ensure aiTopics exists and is an array
|
|
263
|
+
const topics = entry.aiTopics || [];
|
|
264
|
+
if (Array.isArray(topics)) {
|
|
265
|
+
topics.forEach(topic => {
|
|
266
|
+
if (!topicCounts.has(topic)) {
|
|
267
|
+
topicCounts.set(topic, { count: 0, totalImpact: 0, experts: new Set() });
|
|
268
|
+
}
|
|
269
|
+
const topicData = topicCounts.get(topic);
|
|
270
|
+
topicData.count++;
|
|
271
|
+
topicData.totalImpact += entry.impactScore || 0;
|
|
272
|
+
topicData.experts.add(entry.authorId);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
const topTopics = Array.from(topicCounts.entries())
|
|
277
|
+
.map(([topic, data]) => ({
|
|
278
|
+
topic,
|
|
279
|
+
entryCount: data.count,
|
|
280
|
+
expertCount: data.experts.size,
|
|
281
|
+
avgImpactScore: data.count > 0 ? data.totalImpact / data.count : 0,
|
|
282
|
+
recentActivity: recentEntries.filter(e => (e.aiTopics || []).includes(topic)).length
|
|
283
|
+
}))
|
|
284
|
+
.sort((a, b) => b.entryCount - a.entryCount)
|
|
285
|
+
.slice(0, 10);
|
|
286
|
+
const stats = {
|
|
287
|
+
totalEntries,
|
|
288
|
+
pendingApprovals,
|
|
289
|
+
totalExperts,
|
|
290
|
+
activeExperts,
|
|
291
|
+
topTopics,
|
|
292
|
+
recentActivity: {
|
|
293
|
+
newEntries: recentEntries.length,
|
|
294
|
+
approvedEntries: recentEntries.filter(e => e.status === 'approved').length,
|
|
295
|
+
newExperts: recentExperts.length
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
console.log('Admin stats calculated:', JSON.stringify(stats, null, 2));
|
|
299
|
+
return {
|
|
300
|
+
statusCode: 200,
|
|
301
|
+
headers: corsHeaders,
|
|
302
|
+
body: JSON.stringify(stats)
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
console.error('Error getting admin stats:', error);
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
async function getKnowledgeEntries(queryParams) {
|
|
311
|
+
try {
|
|
312
|
+
const { search, status, expertise, limit = '100' } = queryParams;
|
|
313
|
+
// Get all entries from DynamoDB
|
|
314
|
+
const result = await docClient.send(new lib_dynamodb_1.ScanCommand({
|
|
315
|
+
TableName: KNOWLEDGE_TABLE,
|
|
316
|
+
Limit: parseInt(limit)
|
|
317
|
+
}));
|
|
318
|
+
// Parse entries from the stored JSON format
|
|
319
|
+
let entries = [];
|
|
320
|
+
(result.Items || []).forEach(item => {
|
|
321
|
+
if (item.entry_data) {
|
|
322
|
+
try {
|
|
323
|
+
// Document client automatically unmarshals DynamoDB types, so entry_data is already a string
|
|
324
|
+
const entryData = JSON.parse(item.entry_data);
|
|
325
|
+
entries.push({
|
|
326
|
+
...entryData,
|
|
327
|
+
status: entryData.status || 'approved', // Default to approved for existing entries
|
|
328
|
+
assignedExperts: entryData.assignedExperts || []
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
console.warn('Failed to parse entry data:', error, 'Raw item:', JSON.stringify(item));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
// Apply filters
|
|
337
|
+
if (search) {
|
|
338
|
+
entries = entries.filter(entry => entry.content.toLowerCase().includes(search.toLowerCase()) ||
|
|
339
|
+
(entry.aiTopics && entry.aiTopics.some(topic => topic.toLowerCase().includes(search.toLowerCase()))));
|
|
340
|
+
}
|
|
341
|
+
if (status && status !== 'all') {
|
|
342
|
+
entries = entries.filter(entry => entry.status === status);
|
|
343
|
+
}
|
|
344
|
+
if (expertise && expertise !== 'all') {
|
|
345
|
+
entries = entries.filter(entry => entry.expertiseLevel === expertise);
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
statusCode: 200,
|
|
349
|
+
headers: corsHeaders,
|
|
350
|
+
body: JSON.stringify({ entries })
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
console.error('Error getting knowledge entries:', error);
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
async function getExperts() {
|
|
359
|
+
try {
|
|
360
|
+
// Get all entries from DynamoDB
|
|
361
|
+
const entriesResult = await docClient.send(new lib_dynamodb_1.ScanCommand({
|
|
362
|
+
TableName: KNOWLEDGE_TABLE
|
|
363
|
+
}));
|
|
364
|
+
// Parse entries from the stored JSON format
|
|
365
|
+
const entries = [];
|
|
366
|
+
(entriesResult.Items || []).forEach(item => {
|
|
367
|
+
if (item.entry_data) {
|
|
368
|
+
try {
|
|
369
|
+
// Document client automatically unmarshals DynamoDB types, so entry_data is already a string
|
|
370
|
+
const entryData = JSON.parse(item.entry_data);
|
|
371
|
+
entries.push({
|
|
372
|
+
...entryData,
|
|
373
|
+
status: entryData.status || 'approved',
|
|
374
|
+
assignedExperts: entryData.assignedExperts || []
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
console.warn('Failed to parse entry data:', error, 'Raw item:', JSON.stringify(item));
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
// Extract experts from the actual knowledge entries
|
|
383
|
+
const expertMap = new Map();
|
|
384
|
+
entries.forEach(entry => {
|
|
385
|
+
if (entry.authorId) {
|
|
386
|
+
// Handle missing authorName by converting authorId to readable name
|
|
387
|
+
const authorName = entry.authorName || entry.authorId.replace(/-/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase());
|
|
388
|
+
if (!expertMap.has(entry.authorId)) {
|
|
389
|
+
expertMap.set(entry.authorId, {
|
|
390
|
+
id: entry.authorId,
|
|
391
|
+
name: authorName,
|
|
392
|
+
email: `${entry.authorId}@company.com`,
|
|
393
|
+
expertise: [],
|
|
394
|
+
entryCount: 0,
|
|
395
|
+
helpfulnessScore: 0,
|
|
396
|
+
availability: 'medium',
|
|
397
|
+
isActive: true,
|
|
398
|
+
createdAt: entry.createdAt,
|
|
399
|
+
lastActive: entry.createdAt
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
const expert = expertMap.get(entry.authorId);
|
|
403
|
+
expert.entryCount++;
|
|
404
|
+
expert.helpfulnessScore += entry.helpfulnessRating || 0;
|
|
405
|
+
// Add topics to expertise
|
|
406
|
+
if (entry.aiTopics && Array.isArray(entry.aiTopics)) {
|
|
407
|
+
entry.aiTopics.forEach(topic => {
|
|
408
|
+
if (!expert.expertise.includes(topic)) {
|
|
409
|
+
expert.expertise.push(topic);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
// Update last active if this entry is newer
|
|
414
|
+
if (entry.createdAt > expert.lastActive) {
|
|
415
|
+
expert.lastActive = entry.createdAt;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
// Calculate average helpfulness scores and return experts
|
|
420
|
+
const experts = Array.from(expertMap.values()).map(expert => ({
|
|
421
|
+
...expert,
|
|
422
|
+
helpfulnessScore: expert.entryCount > 0 ? expert.helpfulnessScore / expert.entryCount : 0
|
|
423
|
+
}));
|
|
424
|
+
return {
|
|
425
|
+
statusCode: 200,
|
|
426
|
+
headers: corsHeaders,
|
|
427
|
+
body: JSON.stringify({ experts })
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
console.error('Error getting experts:', error);
|
|
432
|
+
throw error;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
async function getTopics() {
|
|
436
|
+
try {
|
|
437
|
+
const result = await docClient.send(new lib_dynamodb_1.ScanCommand({
|
|
438
|
+
TableName: KNOWLEDGE_TABLE
|
|
439
|
+
}));
|
|
440
|
+
// Parse entries from the stored JSON format
|
|
441
|
+
const entries = [];
|
|
442
|
+
(result.Items || []).forEach(item => {
|
|
443
|
+
if (item.entry_data) {
|
|
444
|
+
try {
|
|
445
|
+
const entryData = JSON.parse(item.entry_data);
|
|
446
|
+
entries.push({
|
|
447
|
+
...entryData,
|
|
448
|
+
status: entryData.status || 'approved',
|
|
449
|
+
assignedExperts: entryData.assignedExperts || []
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
console.warn('Failed to parse entry data:', error);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
// Calculate topic statistics
|
|
458
|
+
const topicStats = new Map();
|
|
459
|
+
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
460
|
+
entries.forEach(entry => {
|
|
461
|
+
// Ensure aiTopics exists and is an array
|
|
462
|
+
const topics = entry.aiTopics || [];
|
|
463
|
+
if (Array.isArray(topics)) {
|
|
464
|
+
topics.forEach(topic => {
|
|
465
|
+
if (!topicStats.has(topic)) {
|
|
466
|
+
topicStats.set(topic, {
|
|
467
|
+
entryCount: 0,
|
|
468
|
+
expertCount: 0,
|
|
469
|
+
totalImpact: 0,
|
|
470
|
+
recentActivity: 0,
|
|
471
|
+
experts: new Set()
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
const stats = topicStats.get(topic);
|
|
475
|
+
stats.entryCount++;
|
|
476
|
+
stats.totalImpact += entry.impactScore || 0;
|
|
477
|
+
stats.experts.add(entry.authorId);
|
|
478
|
+
if (entry.createdAt > sevenDaysAgo) {
|
|
479
|
+
stats.recentActivity++;
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
const topics = Array.from(topicStats.entries()).map(([topic, stats]) => ({
|
|
485
|
+
topic,
|
|
486
|
+
entryCount: stats.entryCount,
|
|
487
|
+
expertCount: stats.experts.size,
|
|
488
|
+
avgImpactScore: stats.totalImpact / stats.entryCount,
|
|
489
|
+
recentActivity: stats.recentActivity
|
|
490
|
+
})).sort((a, b) => b.entryCount - a.entryCount);
|
|
491
|
+
return {
|
|
492
|
+
statusCode: 200,
|
|
493
|
+
headers: corsHeaders,
|
|
494
|
+
body: JSON.stringify({ topics })
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
catch (error) {
|
|
498
|
+
console.error('Error getting topics:', error);
|
|
499
|
+
throw error;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
async function searchKnowledge(query) {
|
|
503
|
+
try {
|
|
504
|
+
if (!query.trim()) {
|
|
505
|
+
return {
|
|
506
|
+
statusCode: 400,
|
|
507
|
+
headers: corsHeaders,
|
|
508
|
+
body: JSON.stringify({ error: 'Search query is required' })
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
// Get all entries and filter in memory since we store data as JSON strings
|
|
512
|
+
const result = await docClient.send(new lib_dynamodb_1.ScanCommand({
|
|
513
|
+
TableName: KNOWLEDGE_TABLE,
|
|
514
|
+
Limit: 200 // Get more entries to search through
|
|
515
|
+
}));
|
|
516
|
+
// Parse and filter entries
|
|
517
|
+
const entries = [];
|
|
518
|
+
(result.Items || []).forEach(item => {
|
|
519
|
+
if (item.entry_data) {
|
|
520
|
+
try {
|
|
521
|
+
const entryData = JSON.parse(item.entry_data);
|
|
522
|
+
const entry = {
|
|
523
|
+
...entryData,
|
|
524
|
+
status: entryData.status || 'approved',
|
|
525
|
+
assignedExperts: entryData.assignedExperts || []
|
|
526
|
+
};
|
|
527
|
+
// Check if entry matches search query
|
|
528
|
+
const queryLower = query.toLowerCase();
|
|
529
|
+
const contentMatch = entry.content.toLowerCase().includes(queryLower);
|
|
530
|
+
const topicsMatch = entry.aiTopics && entry.aiTopics.some((topic) => topic.toLowerCase().includes(queryLower));
|
|
531
|
+
const authorName = entry.authorName || entry.authorId.replace(/-/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase());
|
|
532
|
+
const authorMatch = authorName.toLowerCase().includes(queryLower);
|
|
533
|
+
if (contentMatch || topicsMatch || authorMatch) {
|
|
534
|
+
entries.push(entry);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
catch (error) {
|
|
538
|
+
console.warn('Failed to parse entry data during search:', error);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
// Limit results and sort by relevance (could be improved with proper scoring)
|
|
543
|
+
const limitedEntries = entries.slice(0, 50);
|
|
544
|
+
return {
|
|
545
|
+
statusCode: 200,
|
|
546
|
+
headers: corsHeaders,
|
|
547
|
+
body: JSON.stringify({ entries: limitedEntries })
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
console.error('Error searching knowledge:', error);
|
|
552
|
+
throw error;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
// Remaining functions for CRUD operations...
|
|
556
|
+
async function createKnowledgeEntry(entryData) {
|
|
557
|
+
try {
|
|
558
|
+
const entry = {
|
|
559
|
+
id: `entry_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
|
560
|
+
content: entryData.content || '',
|
|
561
|
+
authorId: entryData.authorId || 'admin',
|
|
562
|
+
authorName: entryData.authorName || 'Admin',
|
|
563
|
+
teamId: entryData.teamId || 'default',
|
|
564
|
+
createdAt: new Date().toISOString(),
|
|
565
|
+
aiTopics: entryData.aiTopics || [],
|
|
566
|
+
expertiseLevel: entryData.expertiseLevel || 'intermediate',
|
|
567
|
+
impactScore: entryData.impactScore || 5,
|
|
568
|
+
helpfulnessRating: 0,
|
|
569
|
+
timeSavedMinutes: entryData.timeSavedMinutes || 0,
|
|
570
|
+
viewCount: 0,
|
|
571
|
+
shareCount: 0,
|
|
572
|
+
questionsAnswered: 0,
|
|
573
|
+
onboardingRelevance: entryData.onboardingRelevance || false,
|
|
574
|
+
status: 'approved', // Admin-created entries are auto-approved
|
|
575
|
+
assignedExperts: entryData.assignedExperts || []
|
|
576
|
+
};
|
|
577
|
+
await docClient.send(new lib_dynamodb_1.PutCommand({
|
|
578
|
+
TableName: KNOWLEDGE_TABLE,
|
|
579
|
+
Item: entry
|
|
580
|
+
}));
|
|
581
|
+
return {
|
|
582
|
+
statusCode: 201,
|
|
583
|
+
headers: corsHeaders,
|
|
584
|
+
body: JSON.stringify({ entry })
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
catch (error) {
|
|
588
|
+
console.error('Error creating knowledge entry:', error);
|
|
589
|
+
throw error;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
async function updateKnowledgeEntry(entryId, updates) {
|
|
593
|
+
try {
|
|
594
|
+
const updateExpressions = [];
|
|
595
|
+
const expressionAttributeValues = {};
|
|
596
|
+
const expressionAttributeNames = {};
|
|
597
|
+
Object.entries(updates).forEach(([key, value]) => {
|
|
598
|
+
if (key !== 'id' && value !== undefined) {
|
|
599
|
+
updateExpressions.push(`#${key} = :${key}`);
|
|
600
|
+
expressionAttributeValues[`:${key}`] = value;
|
|
601
|
+
expressionAttributeNames[`#${key}`] = key;
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
if (updateExpressions.length === 0) {
|
|
605
|
+
return {
|
|
606
|
+
statusCode: 400,
|
|
607
|
+
headers: corsHeaders,
|
|
608
|
+
body: JSON.stringify({ error: 'No valid updates provided' })
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
await docClient.send(new lib_dynamodb_1.UpdateCommand({
|
|
612
|
+
TableName: KNOWLEDGE_TABLE,
|
|
613
|
+
Key: { id: entryId },
|
|
614
|
+
UpdateExpression: `SET ${updateExpressions.join(', ')}`,
|
|
615
|
+
ExpressionAttributeValues: expressionAttributeValues,
|
|
616
|
+
ExpressionAttributeNames: expressionAttributeNames
|
|
617
|
+
}));
|
|
618
|
+
return {
|
|
619
|
+
statusCode: 200,
|
|
620
|
+
headers: corsHeaders,
|
|
621
|
+
body: JSON.stringify({ message: 'Entry updated successfully' })
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
catch (error) {
|
|
625
|
+
console.error('Error updating knowledge entry:', error);
|
|
626
|
+
throw error;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
async function approveKnowledgeEntry(entryId) {
|
|
630
|
+
try {
|
|
631
|
+
await docClient.send(new lib_dynamodb_1.UpdateCommand({
|
|
632
|
+
TableName: KNOWLEDGE_TABLE,
|
|
633
|
+
Key: { id: entryId },
|
|
634
|
+
UpdateExpression: 'SET #status = :status, approvedAt = :approvedAt',
|
|
635
|
+
ExpressionAttributeNames: { '#status': 'status' },
|
|
636
|
+
ExpressionAttributeValues: {
|
|
637
|
+
':status': 'approved',
|
|
638
|
+
':approvedAt': new Date().toISOString()
|
|
639
|
+
}
|
|
640
|
+
}));
|
|
641
|
+
return {
|
|
642
|
+
statusCode: 200,
|
|
643
|
+
headers: corsHeaders,
|
|
644
|
+
body: JSON.stringify({ message: 'Entry approved successfully' })
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
catch (error) {
|
|
648
|
+
console.error('Error approving knowledge entry:', error);
|
|
649
|
+
throw error;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
async function rejectKnowledgeEntry(entryId, reason) {
|
|
653
|
+
try {
|
|
654
|
+
await docClient.send(new lib_dynamodb_1.UpdateCommand({
|
|
655
|
+
TableName: KNOWLEDGE_TABLE,
|
|
656
|
+
Key: { id: entryId },
|
|
657
|
+
UpdateExpression: 'SET #status = :status, rejectedAt = :rejectedAt, rejectionReason = :reason',
|
|
658
|
+
ExpressionAttributeNames: { '#status': 'status' },
|
|
659
|
+
ExpressionAttributeValues: {
|
|
660
|
+
':status': 'rejected',
|
|
661
|
+
':rejectedAt': new Date().toISOString(),
|
|
662
|
+
':reason': reason || 'No reason provided'
|
|
663
|
+
}
|
|
664
|
+
}));
|
|
665
|
+
return {
|
|
666
|
+
statusCode: 200,
|
|
667
|
+
headers: corsHeaders,
|
|
668
|
+
body: JSON.stringify({ message: 'Entry rejected successfully' })
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
catch (error) {
|
|
672
|
+
console.error('Error rejecting knowledge entry:', error);
|
|
673
|
+
throw error;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
async function assignExpertToEntry(entryId, expertId) {
|
|
677
|
+
try {
|
|
678
|
+
await docClient.send(new lib_dynamodb_1.UpdateCommand({
|
|
679
|
+
TableName: KNOWLEDGE_TABLE,
|
|
680
|
+
Key: { id: entryId },
|
|
681
|
+
UpdateExpression: 'ADD assignedExperts :expertId',
|
|
682
|
+
ExpressionAttributeValues: {
|
|
683
|
+
':expertId': new Set([expertId])
|
|
684
|
+
}
|
|
685
|
+
}));
|
|
686
|
+
return {
|
|
687
|
+
statusCode: 200,
|
|
688
|
+
headers: corsHeaders,
|
|
689
|
+
body: JSON.stringify({ message: 'Expert assigned successfully' })
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
catch (error) {
|
|
693
|
+
console.error('Error assigning expert to entry:', error);
|
|
694
|
+
throw error;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
async function bulkApproveEntries(entryIds) {
|
|
698
|
+
try {
|
|
699
|
+
const approvedAt = new Date().toISOString();
|
|
700
|
+
// Process in batches to avoid DynamoDB limits
|
|
701
|
+
const batchSize = 25;
|
|
702
|
+
for (let i = 0; i < entryIds.length; i += batchSize) {
|
|
703
|
+
const batch = entryIds.slice(i, i + batchSize);
|
|
704
|
+
await Promise.all(batch.map(entryId => docClient.send(new lib_dynamodb_1.UpdateCommand({
|
|
705
|
+
TableName: KNOWLEDGE_TABLE,
|
|
706
|
+
Key: { id: entryId },
|
|
707
|
+
UpdateExpression: 'SET #status = :status, approvedAt = :approvedAt',
|
|
708
|
+
ExpressionAttributeNames: { '#status': 'status' },
|
|
709
|
+
ExpressionAttributeValues: {
|
|
710
|
+
':status': 'approved',
|
|
711
|
+
':approvedAt': approvedAt
|
|
712
|
+
}
|
|
713
|
+
}))));
|
|
714
|
+
}
|
|
715
|
+
return {
|
|
716
|
+
statusCode: 200,
|
|
717
|
+
headers: corsHeaders,
|
|
718
|
+
body: JSON.stringify({
|
|
719
|
+
message: `${entryIds.length} entries approved successfully`
|
|
720
|
+
})
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
catch (error) {
|
|
724
|
+
console.error('Error bulk approving entries:', error);
|
|
725
|
+
throw error;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
async function createExpert(expertData) {
|
|
729
|
+
try {
|
|
730
|
+
const expert = {
|
|
731
|
+
id: `expert_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
|
732
|
+
name: expertData.name || '',
|
|
733
|
+
email: expertData.email || '',
|
|
734
|
+
expertise: expertData.expertise || [],
|
|
735
|
+
entryCount: 0,
|
|
736
|
+
helpfulnessScore: 0,
|
|
737
|
+
availability: expertData.availability || 'medium',
|
|
738
|
+
isActive: true,
|
|
739
|
+
createdAt: new Date().toISOString(),
|
|
740
|
+
lastActive: new Date().toISOString()
|
|
741
|
+
};
|
|
742
|
+
await docClient.send(new lib_dynamodb_1.PutCommand({
|
|
743
|
+
TableName: KNOWLEDGE_TABLE,
|
|
744
|
+
Item: expert
|
|
745
|
+
}));
|
|
746
|
+
return {
|
|
747
|
+
statusCode: 201,
|
|
748
|
+
headers: corsHeaders,
|
|
749
|
+
body: JSON.stringify({ expert })
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
catch (error) {
|
|
753
|
+
console.error('Error creating expert:', error);
|
|
754
|
+
throw error;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
async function toggleExpertStatus(expertId) {
|
|
758
|
+
try {
|
|
759
|
+
// First get the current status
|
|
760
|
+
const result = await docClient.send(new lib_dynamodb_1.QueryCommand({
|
|
761
|
+
TableName: KNOWLEDGE_TABLE,
|
|
762
|
+
KeyConditionExpression: 'id = :id',
|
|
763
|
+
ExpressionAttributeValues: { ':id': expertId }
|
|
764
|
+
}));
|
|
765
|
+
if (!result.Items || result.Items.length === 0) {
|
|
766
|
+
return {
|
|
767
|
+
statusCode: 404,
|
|
768
|
+
headers: corsHeaders,
|
|
769
|
+
body: JSON.stringify({ error: 'Expert not found' })
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
const expert = result.Items[0];
|
|
773
|
+
const newStatus = !expert.isActive;
|
|
774
|
+
await docClient.send(new lib_dynamodb_1.UpdateCommand({
|
|
775
|
+
TableName: KNOWLEDGE_TABLE,
|
|
776
|
+
Key: { id: expertId },
|
|
777
|
+
UpdateExpression: 'SET isActive = :status, lastActive = :lastActive',
|
|
778
|
+
ExpressionAttributeValues: {
|
|
779
|
+
':status': newStatus,
|
|
780
|
+
':lastActive': new Date().toISOString()
|
|
781
|
+
}
|
|
782
|
+
}));
|
|
783
|
+
return {
|
|
784
|
+
statusCode: 200,
|
|
785
|
+
headers: corsHeaders,
|
|
786
|
+
body: JSON.stringify({
|
|
787
|
+
message: `Expert ${newStatus ? 'activated' : 'deactivated'} successfully`
|
|
788
|
+
})
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
catch (error) {
|
|
792
|
+
console.error('Error toggling expert status:', error);
|
|
793
|
+
throw error;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
async function updateExpertAvailability(expertId, availability) {
|
|
797
|
+
try {
|
|
798
|
+
await docClient.send(new lib_dynamodb_1.UpdateCommand({
|
|
799
|
+
TableName: KNOWLEDGE_TABLE,
|
|
800
|
+
Key: { id: expertId },
|
|
801
|
+
UpdateExpression: 'SET availability = :availability, lastActive = :lastActive',
|
|
802
|
+
ExpressionAttributeValues: {
|
|
803
|
+
':availability': availability,
|
|
804
|
+
':lastActive': new Date().toISOString()
|
|
805
|
+
}
|
|
806
|
+
}));
|
|
807
|
+
return {
|
|
808
|
+
statusCode: 200,
|
|
809
|
+
headers: corsHeaders,
|
|
810
|
+
body: JSON.stringify({ message: 'Expert availability updated successfully' })
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
catch (error) {
|
|
814
|
+
console.error('Error updating expert availability:', error);
|
|
815
|
+
throw error;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
//# sourceMappingURL=admin.js.map
|