@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.
Files changed (210) hide show
  1. package/README.md +296 -0
  2. package/dist/ai/bedrock-client.d.ts +1 -0
  3. package/dist/ai/bedrock-client.d.ts.map +1 -0
  4. package/dist/ai/bedrock-client.js +2 -0
  5. package/dist/ai/bedrock-client.js.map +1 -0
  6. package/dist/ai/knowledge-processor.d.ts +1 -0
  7. package/dist/ai/knowledge-processor.d.ts.map +1 -0
  8. package/dist/ai/knowledge-processor.js +2 -0
  9. package/dist/ai/knowledge-processor.js.map +1 -0
  10. package/dist/cli.d.ts +3 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +70 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/commands/ask.d.ts +3 -0
  15. package/dist/commands/ask.d.ts.map +1 -0
  16. package/dist/commands/ask.js +217 -0
  17. package/dist/commands/ask.js.map +1 -0
  18. package/dist/commands/audit.d.ts +3 -0
  19. package/dist/commands/audit.d.ts.map +1 -0
  20. package/dist/commands/audit.js +153 -0
  21. package/dist/commands/audit.js.map +1 -0
  22. package/dist/commands/auth.d.ts +3 -0
  23. package/dist/commands/auth.d.ts.map +1 -0
  24. package/dist/commands/auth.js +114 -0
  25. package/dist/commands/auth.js.map +1 -0
  26. package/dist/commands/config.d.ts +3 -0
  27. package/dist/commands/config.d.ts.map +1 -0
  28. package/dist/commands/config.js +72 -0
  29. package/dist/commands/config.js.map +1 -0
  30. package/dist/commands/embeddings.d.ts +3 -0
  31. package/dist/commands/embeddings.d.ts.map +1 -0
  32. package/dist/commands/embeddings.js +118 -0
  33. package/dist/commands/embeddings.js.map +1 -0
  34. package/dist/commands/experts.d.ts +3 -0
  35. package/dist/commands/experts.d.ts.map +1 -0
  36. package/dist/commands/experts.js +129 -0
  37. package/dist/commands/experts.js.map +1 -0
  38. package/dist/commands/export-import.d.ts +4 -0
  39. package/dist/commands/export-import.d.ts.map +1 -0
  40. package/dist/commands/export-import.js +285 -0
  41. package/dist/commands/export-import.js.map +1 -0
  42. package/dist/commands/git.d.ts +4 -0
  43. package/dist/commands/git.d.ts.map +1 -0
  44. package/dist/commands/git.js +274 -0
  45. package/dist/commands/git.js.map +1 -0
  46. package/dist/commands/impact.d.ts +3 -0
  47. package/dist/commands/impact.d.ts.map +1 -0
  48. package/dist/commands/impact.js +121 -0
  49. package/dist/commands/impact.js.map +1 -0
  50. package/dist/commands/insights.d.ts +3 -0
  51. package/dist/commands/insights.d.ts.map +1 -0
  52. package/dist/commands/insights.js +113 -0
  53. package/dist/commands/insights.js.map +1 -0
  54. package/dist/commands/learned.d.ts +3 -0
  55. package/dist/commands/learned.d.ts.map +1 -0
  56. package/dist/commands/learned.js +355 -0
  57. package/dist/commands/learned.js.map +1 -0
  58. package/dist/commands/onboard.d.ts +3 -0
  59. package/dist/commands/onboard.d.ts.map +1 -0
  60. package/dist/commands/onboard.js +141 -0
  61. package/dist/commands/onboard.js.map +1 -0
  62. package/dist/commands/recent.d.ts +3 -0
  63. package/dist/commands/recent.d.ts.map +1 -0
  64. package/dist/commands/recent.js +83 -0
  65. package/dist/commands/recent.js.map +1 -0
  66. package/dist/commands/secrets.d.ts +3 -0
  67. package/dist/commands/secrets.d.ts.map +1 -0
  68. package/dist/commands/secrets.js +203 -0
  69. package/dist/commands/secrets.js.map +1 -0
  70. package/dist/commands/stats.d.ts +3 -0
  71. package/dist/commands/stats.d.ts.map +1 -0
  72. package/dist/commands/stats.js +120 -0
  73. package/dist/commands/stats.js.map +1 -0
  74. package/dist/config/index.d.ts +12 -0
  75. package/dist/config/index.d.ts.map +1 -0
  76. package/dist/config/index.js +46 -0
  77. package/dist/config/index.js.map +1 -0
  78. package/dist/database/migrations.d.ts +13 -0
  79. package/dist/database/migrations.d.ts.map +1 -0
  80. package/dist/database/migrations.js +190 -0
  81. package/dist/database/migrations.js.map +1 -0
  82. package/dist/database/postgresql.d.ts +92 -0
  83. package/dist/database/postgresql.d.ts.map +1 -0
  84. package/dist/database/postgresql.js +382 -0
  85. package/dist/database/postgresql.js.map +1 -0
  86. package/dist/database/sqlite.d.ts +54 -0
  87. package/dist/database/sqlite.d.ts.map +1 -0
  88. package/dist/database/sqlite.js +337 -0
  89. package/dist/database/sqlite.js.map +1 -0
  90. package/dist/lambda/admin.d.ts +3 -0
  91. package/dist/lambda/admin.d.ts.map +1 -0
  92. package/dist/lambda/admin.js +818 -0
  93. package/dist/lambda/admin.js.map +1 -0
  94. package/dist/lambda/ai-services.d.ts +6 -0
  95. package/dist/lambda/ai-services.d.ts.map +1 -0
  96. package/dist/lambda/ai-services.js +472 -0
  97. package/dist/lambda/ai-services.js.map +1 -0
  98. package/dist/lambda/analytics.d.ts +3 -0
  99. package/dist/lambda/analytics.d.ts.map +1 -0
  100. package/dist/lambda/analytics.js +481 -0
  101. package/dist/lambda/analytics.js.map +1 -0
  102. package/dist/lambda/api-router.d.ts +3 -0
  103. package/dist/lambda/api-router.d.ts.map +1 -0
  104. package/dist/lambda/api-router.js +162 -0
  105. package/dist/lambda/api-router.js.map +1 -0
  106. package/dist/lambda/custom-topics.d.ts +3 -0
  107. package/dist/lambda/custom-topics.d.ts.map +1 -0
  108. package/dist/lambda/custom-topics.js +425 -0
  109. package/dist/lambda/custom-topics.js.map +1 -0
  110. package/dist/lambda/graph-builder.d.ts +3 -0
  111. package/dist/lambda/graph-builder.d.ts.map +1 -0
  112. package/dist/lambda/graph-builder.js +442 -0
  113. package/dist/lambda/graph-builder.js.map +1 -0
  114. package/dist/lambda/knowledge-ai.d.ts +3 -0
  115. package/dist/lambda/knowledge-ai.d.ts.map +1 -0
  116. package/dist/lambda/knowledge-ai.js +849 -0
  117. package/dist/lambda/knowledge-ai.js.map +1 -0
  118. package/dist/lambda/post-confirmation.d.ts +8 -0
  119. package/dist/lambda/post-confirmation.d.ts.map +1 -0
  120. package/dist/lambda/post-confirmation.js +92 -0
  121. package/dist/lambda/post-confirmation.js.map +1 -0
  122. package/dist/lambda/teams.d.ts +3 -0
  123. package/dist/lambda/teams.d.ts.map +1 -0
  124. package/dist/lambda/teams.js +568 -0
  125. package/dist/lambda/teams.js.map +1 -0
  126. package/dist/lib/export-helpers.d.ts +5 -0
  127. package/dist/lib/export-helpers.d.ts.map +1 -0
  128. package/dist/lib/export-helpers.js +137 -0
  129. package/dist/lib/export-helpers.js.map +1 -0
  130. package/dist/lib/import-helpers.d.ts +5 -0
  131. package/dist/lib/import-helpers.d.ts.map +1 -0
  132. package/dist/lib/import-helpers.js +185 -0
  133. package/dist/lib/import-helpers.js.map +1 -0
  134. package/dist/lib/insights-helpers.d.ts +123 -0
  135. package/dist/lib/insights-helpers.d.ts.map +1 -0
  136. package/dist/lib/insights-helpers.js +374 -0
  137. package/dist/lib/insights-helpers.js.map +1 -0
  138. package/dist/lib/search-helpers.d.ts +4 -0
  139. package/dist/lib/search-helpers.d.ts.map +1 -0
  140. package/dist/lib/search-helpers.js +124 -0
  141. package/dist/lib/search-helpers.js.map +1 -0
  142. package/dist/lib/secret-helpers.d.ts +2 -0
  143. package/dist/lib/secret-helpers.d.ts.map +1 -0
  144. package/dist/lib/secret-helpers.js +85 -0
  145. package/dist/lib/secret-helpers.js.map +1 -0
  146. package/dist/lib/stats-helpers.d.ts +41 -0
  147. package/dist/lib/stats-helpers.d.ts.map +1 -0
  148. package/dist/lib/stats-helpers.js +263 -0
  149. package/dist/lib/stats-helpers.js.map +1 -0
  150. package/dist/services/aws-api.d.ts +81 -0
  151. package/dist/services/aws-api.d.ts.map +1 -0
  152. package/dist/services/aws-api.js +388 -0
  153. package/dist/services/aws-api.js.map +1 -0
  154. package/dist/services/bedrock.d.ts +83 -0
  155. package/dist/services/bedrock.d.ts.map +1 -0
  156. package/dist/services/bedrock.js +434 -0
  157. package/dist/services/bedrock.js.map +1 -0
  158. package/dist/services/commit-learnings.d.ts +25 -0
  159. package/dist/services/commit-learnings.d.ts.map +1 -0
  160. package/dist/services/commit-learnings.js +180 -0
  161. package/dist/services/commit-learnings.js.map +1 -0
  162. package/dist/services/embedding-storage.d.ts +42 -0
  163. package/dist/services/embedding-storage.d.ts.map +1 -0
  164. package/dist/services/embedding-storage.js +124 -0
  165. package/dist/services/embedding-storage.js.map +1 -0
  166. package/dist/services/expert-engine.d.ts +21 -0
  167. package/dist/services/expert-engine.d.ts.map +1 -0
  168. package/dist/services/expert-engine.js +58 -0
  169. package/dist/services/expert-engine.js.map +1 -0
  170. package/dist/services/onboarding-accelerator.d.ts +118 -0
  171. package/dist/services/onboarding-accelerator.d.ts.map +1 -0
  172. package/dist/services/onboarding-accelerator.js +403 -0
  173. package/dist/services/onboarding-accelerator.js.map +1 -0
  174. package/dist/services/secret-detection.d.ts +46 -0
  175. package/dist/services/secret-detection.d.ts.map +1 -0
  176. package/dist/services/secret-detection.js +75 -0
  177. package/dist/services/secret-detection.js.map +1 -0
  178. package/dist/services/secret-manager-simple.d.ts +51 -0
  179. package/dist/services/secret-manager-simple.d.ts.map +1 -0
  180. package/dist/services/secret-manager-simple.js +119 -0
  181. package/dist/services/secret-manager-simple.js.map +1 -0
  182. package/dist/services/secret-manager.d.ts +150 -0
  183. package/dist/services/secret-manager.d.ts.map +1 -0
  184. package/dist/services/secret-manager.js +287 -0
  185. package/dist/services/secret-manager.js.map +1 -0
  186. package/dist/services/vector-embeddings.d.ts +70 -0
  187. package/dist/services/vector-embeddings.d.ts.map +1 -0
  188. package/dist/services/vector-embeddings.js +167 -0
  189. package/dist/services/vector-embeddings.js.map +1 -0
  190. package/dist/services/vector-search.d.ts +28 -0
  191. package/dist/services/vector-search.d.ts.map +1 -0
  192. package/dist/services/vector-search.js +192 -0
  193. package/dist/services/vector-search.js.map +1 -0
  194. package/dist/types/index.d.ts +86 -0
  195. package/dist/types/index.d.ts.map +1 -0
  196. package/dist/types/index.js +4 -0
  197. package/dist/types/index.js.map +1 -0
  198. package/dist/utils/formatting.d.ts +10 -0
  199. package/dist/utils/formatting.d.ts.map +1 -0
  200. package/dist/utils/formatting.js +82 -0
  201. package/dist/utils/formatting.js.map +1 -0
  202. package/dist/utils/git.d.ts +52 -0
  203. package/dist/utils/git.d.ts.map +1 -0
  204. package/dist/utils/git.js +182 -0
  205. package/dist/utils/git.js.map +1 -0
  206. package/dist/utils/validation.d.ts +17 -0
  207. package/dist/utils/validation.d.ts.map +1 -0
  208. package/dist/utils/validation.js +187 -0
  209. package/dist/utils/validation.js.map +1 -0
  210. 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