hydro-ai-helper 1.8.5

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 (182) hide show
  1. package/.eslintrc.json +31 -0
  2. package/.github/workflows/sync-releases-to-gitee.yml +119 -0
  3. package/.github/workflows/sync-to-gitee.yml +53 -0
  4. package/.vercelignore +31 -0
  5. package/CONTRIBUTING.md +165 -0
  6. package/README.md +273 -0
  7. package/TROUBLESHOOTING.md +355 -0
  8. package/api/badge-active.ts +88 -0
  9. package/api/badge-installs.ts +75 -0
  10. package/api/report.ts +242 -0
  11. package/assets/screenshots/1.png +0 -0
  12. package/assets/screenshots/2.png +0 -0
  13. package/assets/screenshots/3.png +0 -0
  14. package/assets/screenshots/4.png +0 -0
  15. package/assets/screenshots/5.png +0 -0
  16. package/assets/screenshots/6.png +0 -0
  17. package/assets/screenshots/7.png +0 -0
  18. package/dist/__tests__/__mocks__/hydrooj.js +29 -0
  19. package/dist/__tests__/__mocks__/hydrooj.js.map +1 -0
  20. package/dist/constants/jailbreakRules.js +29 -0
  21. package/dist/constants/jailbreakRules.js.map +1 -0
  22. package/dist/constants/permissions.js +42 -0
  23. package/dist/constants/permissions.js.map +1 -0
  24. package/dist/handlers/adminConfigHandler.js +347 -0
  25. package/dist/handlers/adminConfigHandler.js.map +1 -0
  26. package/dist/handlers/adminHandler.js +378 -0
  27. package/dist/handlers/adminHandler.js.map +1 -0
  28. package/dist/handlers/analyticsHandler.js +433 -0
  29. package/dist/handlers/analyticsHandler.js.map +1 -0
  30. package/dist/handlers/dashboardHandler.js +22 -0
  31. package/dist/handlers/dashboardHandler.js.map +1 -0
  32. package/dist/handlers/exportHandler.js +118 -0
  33. package/dist/handlers/exportHandler.js.map +1 -0
  34. package/dist/handlers/studentHandler.js +430 -0
  35. package/dist/handlers/studentHandler.js.map +1 -0
  36. package/dist/handlers/teacherHandler.js +246 -0
  37. package/dist/handlers/teacherHandler.js.map +1 -0
  38. package/dist/handlers/testHandler.js +27 -0
  39. package/dist/handlers/testHandler.js.map +1 -0
  40. package/dist/handlers/updateHandler.js +84 -0
  41. package/dist/handlers/updateHandler.js.map +1 -0
  42. package/dist/handlers/versionHandler.js +61 -0
  43. package/dist/handlers/versionHandler.js.map +1 -0
  44. package/dist/index.d.ts +22 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +151 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/lib/crypto.js +110 -0
  49. package/dist/lib/crypto.js.map +1 -0
  50. package/dist/lib/httpHelpers.js +61 -0
  51. package/dist/lib/httpHelpers.js.map +1 -0
  52. package/dist/lib/queryHelpers.js +81 -0
  53. package/dist/lib/queryHelpers.js.map +1 -0
  54. package/dist/models/aiConfig.js +255 -0
  55. package/dist/models/aiConfig.js.map +1 -0
  56. package/dist/models/conversation.js +209 -0
  57. package/dist/models/conversation.js.map +1 -0
  58. package/dist/models/jailbreakLog.js +68 -0
  59. package/dist/models/jailbreakLog.js.map +1 -0
  60. package/dist/models/message.js +174 -0
  61. package/dist/models/message.js.map +1 -0
  62. package/dist/models/pluginInstall.js +95 -0
  63. package/dist/models/pluginInstall.js.map +1 -0
  64. package/dist/models/rateLimitRecord.js +54 -0
  65. package/dist/models/rateLimitRecord.js.map +1 -0
  66. package/dist/models/versionCache.js +89 -0
  67. package/dist/models/versionCache.js.map +1 -0
  68. package/dist/services/effectivenessService.js +131 -0
  69. package/dist/services/effectivenessService.js.map +1 -0
  70. package/dist/services/exportService.js +152 -0
  71. package/dist/services/exportService.js.map +1 -0
  72. package/dist/services/migrationService.js +90 -0
  73. package/dist/services/migrationService.js.map +1 -0
  74. package/dist/services/openaiClient.js +412 -0
  75. package/dist/services/openaiClient.js.map +1 -0
  76. package/dist/services/promptService.js +467 -0
  77. package/dist/services/promptService.js.map +1 -0
  78. package/dist/services/rateLimitService.js +108 -0
  79. package/dist/services/rateLimitService.js.map +1 -0
  80. package/dist/services/telemetryService.js +173 -0
  81. package/dist/services/telemetryService.js.map +1 -0
  82. package/dist/services/updateService.js +388 -0
  83. package/dist/services/updateService.js.map +1 -0
  84. package/dist/services/versionService.js +289 -0
  85. package/dist/services/versionService.js.map +1 -0
  86. package/dist/types/hydrooj.js +7 -0
  87. package/dist/types/hydrooj.js.map +1 -0
  88. package/dist/types/mongo.js +7 -0
  89. package/dist/types/mongo.js.map +1 -0
  90. package/dist/utils/domainHelper.js +16 -0
  91. package/dist/utils/domainHelper.js.map +1 -0
  92. package/dist/utils/mongo.js +16 -0
  93. package/dist/utils/mongo.js.map +1 -0
  94. package/frontend/admin/ConfigPanel.tsx +1379 -0
  95. package/frontend/admin/VersionBadge.tsx +655 -0
  96. package/frontend/ai_helper.page.tsx +52 -0
  97. package/frontend/ai_helper_admin_config.page.tsx +47 -0
  98. package/frontend/ai_helper_analytics.page.tsx +52 -0
  99. package/frontend/ai_helper_conversation_detail.page.tsx +55 -0
  100. package/frontend/ai_helper_conversations.page.tsx +56 -0
  101. package/frontend/components/AIHelperDashboard.tsx +125 -0
  102. package/frontend/problem_detail.page.tsx +395 -0
  103. package/frontend/student/AIAssistantPanel.tsx +1839 -0
  104. package/frontend/student/AIChatPanel.tsx +944 -0
  105. package/frontend/student/ThreeColumnLayout.tsx +240 -0
  106. package/frontend/teacher/AnalyticsPage.tsx +614 -0
  107. package/frontend/teacher/ConversationDetail.tsx +504 -0
  108. package/frontend/teacher/ConversationList.tsx +568 -0
  109. package/frontend/teacher/ExportDialog.tsx +291 -0
  110. package/frontend/test.page.ts +88 -0
  111. package/frontend/utils/domainUtils.ts +37 -0
  112. package/index.js +6 -0
  113. package/jest.config.js +17 -0
  114. package/lib/mongodb.ts +60 -0
  115. package/locales/en.yaml +4 -0
  116. package/locales/zh.yaml +77 -0
  117. package/package.json +48 -0
  118. package/public/.gitkeep +0 -0
  119. package/public/README.md +1 -0
  120. package/src/__tests__/__mocks__/hydrooj.ts +27 -0
  121. package/src/__tests__/services/exportService.test.ts +156 -0
  122. package/src/__tests__/services/promptService.test.ts +141 -0
  123. package/src/__tests__/services/rateLimitService.test.ts +145 -0
  124. package/src/__tests__/utils/domainHelper.test.ts +47 -0
  125. package/src/constants/jailbreakRules.ts +28 -0
  126. package/src/constants/permissions.ts +43 -0
  127. package/src/handlers/adminConfigHandler.ts +363 -0
  128. package/src/handlers/adminHandler.ts +427 -0
  129. package/src/handlers/analyticsHandler.ts +517 -0
  130. package/src/handlers/dashboardHandler.ts +20 -0
  131. package/src/handlers/exportHandler.ts +134 -0
  132. package/src/handlers/studentHandler.ts +483 -0
  133. package/src/handlers/teacherHandler.ts +317 -0
  134. package/src/handlers/testHandler.ts +25 -0
  135. package/src/handlers/updateHandler.ts +85 -0
  136. package/src/handlers/versionHandler.ts +62 -0
  137. package/src/index.ts +198 -0
  138. package/src/lib/crypto.ts +125 -0
  139. package/src/lib/httpHelpers.ts +75 -0
  140. package/src/lib/queryHelpers.ts +108 -0
  141. package/src/models/aiConfig.ts +375 -0
  142. package/src/models/conversation.ts +328 -0
  143. package/src/models/jailbreakLog.ts +108 -0
  144. package/src/models/message.ts +246 -0
  145. package/src/models/pluginInstall.ts +138 -0
  146. package/src/models/rateLimitRecord.ts +81 -0
  147. package/src/models/versionCache.ts +127 -0
  148. package/src/services/effectivenessService.ts +161 -0
  149. package/src/services/exportService.ts +191 -0
  150. package/src/services/migrationService.ts +111 -0
  151. package/src/services/openaiClient.ts +548 -0
  152. package/src/services/promptService.ts +573 -0
  153. package/src/services/rateLimitService.ts +122 -0
  154. package/src/services/telemetryService.ts +218 -0
  155. package/src/services/updateService.ts +441 -0
  156. package/src/services/versionService.ts +351 -0
  157. package/src/types/hydrooj-extensions.d.ts +55 -0
  158. package/src/types/hydrooj.ts +38 -0
  159. package/src/types/mongo.ts +23 -0
  160. package/src/utils/domainHelper.ts +16 -0
  161. package/src/utils/mongo.ts +15 -0
  162. package/templates/ai-helper/admin_config.html +8 -0
  163. package/templates/ai-helper/analytics.html +8 -0
  164. package/templates/ai-helper/dashboard.html +8 -0
  165. package/templates/ai-helper/teacher_conversation_detail.html +8 -0
  166. package/templates/ai-helper/teacher_conversations.html +8 -0
  167. package/tests/integration/README.md +86 -0
  168. package/tests/integration/curl/01-admin-config-set.sh +56 -0
  169. package/tests/integration/curl/02-student-chat.sh +67 -0
  170. package/tests/integration/curl/03-teacher-list.sh +52 -0
  171. package/tests/integration/curl/04-teacher-detail.sh +37 -0
  172. package/tests/integration/curl/05-export.sh +49 -0
  173. package/tests/integration/curl/06-analytics.sh +43 -0
  174. package/tests/performance/README.md +85 -0
  175. package/tests/performance/data/prepare-sample-data.md +57 -0
  176. package/tests/performance/scripts/export-csv-k6.js +41 -0
  177. package/tests/performance/scripts/student-chat-k6.js +44 -0
  178. package/tests/performance/scripts/teacher-conversations-k6.js +33 -0
  179. package/tsconfig.json +42 -0
  180. package/tsconfig.vercel.json +27 -0
  181. package/types/hydrooj/index.d.ts +73 -0
  182. package/vercel.json +3 -0
@@ -0,0 +1,355 @@
1
+ # Vercel 部署故障排查
2
+
3
+ ## 问题:构建失败 - TypeScript 编译错误
4
+
5
+ ### 错误现象
6
+
7
+ ```
8
+ Running "npm run build"
9
+ > hydro-ai-helper@1.8.0 build
10
+ > tsc
11
+ ```
12
+
13
+ 然后构建失败或超时。
14
+
15
+ ### 原因分析
16
+
17
+ Vercel 默认会运行 `npm run build`,但我们的 `tsconfig.json` 是为 HydroOJ 插件设计的,包含了 `src/` 和 `frontend/` 目录,这些目录依赖 HydroOJ 的类型定义,在 Vercel 环境中无法编译。
18
+
19
+ ### 解决方案
20
+
21
+ #### ✅ 方案 1:禁用构建命令(已实施)
22
+
23
+ 在 `vercel.json` 中添加 `buildCommand: null`:
24
+
25
+ ```json
26
+ {
27
+ "version": 2,
28
+ "buildCommand": null,
29
+ "functions": {
30
+ "api/*.ts": {
31
+ "runtime": "nodejs20.x"
32
+ }
33
+ }
34
+ }
35
+ ```
36
+
37
+ **原理**:Vercel Functions 原生支持 TypeScript,不需要预编译。
38
+
39
+ #### 方案 2:创建 Vercel 专用配置(备选)
40
+
41
+ 创建 `tsconfig.vercel.json`,只编译 `api/` 和 `lib/` 目录:
42
+
43
+ ```json
44
+ {
45
+ "compilerOptions": {
46
+ "target": "ES2020",
47
+ "module": "commonjs",
48
+ "outDir": ".vercel/output",
49
+ "strict": false,
50
+ "skipLibCheck": true
51
+ },
52
+ "include": ["api/**/*", "lib/**/*"],
53
+ "exclude": ["node_modules", "src", "frontend", "dist"]
54
+ }
55
+ ```
56
+
57
+ 然后修改 `package.json`:
58
+
59
+ ```json
60
+ {
61
+ "scripts": {
62
+ "build": "tsc -p tsconfig.vercel.json",
63
+ "build:plugin": "tsc"
64
+ }
65
+ }
66
+ ```
67
+
68
+ ---
69
+
70
+ ## 问题:MongoDB 连接失败
71
+
72
+ ### 错误现象
73
+
74
+ Vercel 函数日志显示:
75
+ ```
76
+ MongoServerError: bad auth : authentication failed
77
+ ```
78
+
79
+ ### 解决方案
80
+
81
+ 1. **检查环境变量**
82
+ - Vercel Dashboard → 项目 → Settings → Environment Variables
83
+ - 确认 `MONGODB_URI` 和 `MONGODB_DB` 已设置
84
+
85
+ 2. **检查密码编码**
86
+ - 如果密码包含特殊字符,需要 URL 编码:
87
+ - `@` → `%40`
88
+ - `#` → `%23`
89
+ - `%` → `%25`
90
+
91
+ 3. **检查网络访问**
92
+ - MongoDB Atlas → Network Access
93
+ - 确认包含 `0.0.0.0/0`(允许所有 IP)
94
+
95
+ 4. **测试连接字符串**
96
+ ```bash
97
+ # 使用 mongosh 测试
98
+ mongosh "mongodb+srv://user:pass@cluster.mongodb.net/"
99
+ ```
100
+
101
+ ---
102
+
103
+ ## 问题:函数超时
104
+
105
+ ### 错误现象
106
+
107
+ ```
108
+ Function execution timed out
109
+ ```
110
+
111
+ ### 解决方案
112
+
113
+ 1. **检查 MongoDB 集群状态**
114
+ - 免费集群会自动暂停
115
+ - 在 MongoDB Atlas 中手动唤醒
116
+
117
+ 2. **优化连接池**
118
+ - 已在 `lib/mongodb.ts` 中实现
119
+ - 使用全局变量缓存连接
120
+
121
+ 3. **增加超时时间**(如果需要)
122
+ ```json
123
+ // vercel.json
124
+ {
125
+ "functions": {
126
+ "api/*.ts": {
127
+ "runtime": "nodejs20.x",
128
+ "maxDuration": 10
129
+ }
130
+ }
131
+ }
132
+ ```
133
+
134
+ ---
135
+
136
+ ## 问题:徽章显示 "invalid"
137
+
138
+ ### 错误现象
139
+
140
+ GitHub README 徽章显示灰色 "invalid"。
141
+
142
+ ### 解决方案
143
+
144
+ 1. **测试 API 端点**
145
+ ```bash
146
+ curl https://your-vercel-app.vercel.app/api/badge-installs
147
+ ```
148
+
149
+ 2. **检查返回格式**
150
+ - 必须是 Shields.io JSON 格式:
151
+ ```json
152
+ {
153
+ "schemaVersion": 1,
154
+ "label": "installations",
155
+ "message": "123",
156
+ "color": "blue"
157
+ }
158
+ ```
159
+
160
+ 3. **查看 Vercel 日志**
161
+ - Vercel Dashboard → 项目 → Functions → Logs
162
+ - 查看具体错误信息
163
+
164
+ 4. **检查 CORS**
165
+ - 徽章端点已配置 CORS
166
+ - 确认 `Access-Control-Allow-Origin: *` 头存在
167
+
168
+ ---
169
+
170
+ ## 问题:徽章显示 "error"
171
+
172
+ ### 错误现象
173
+
174
+ 徽章显示红色 "error"。
175
+
176
+ ### 解决方案
177
+
178
+ 1. **检查数据库连接**
179
+ - 确认 MongoDB 连接正常
180
+ - 查看 Vercel 函数日志
181
+
182
+ 2. **检查数据**
183
+ ```javascript
184
+ // 使用 MongoDB Compass 或 mongosh
185
+ use hydro_ai_stats
186
+ db.plugin_stats.find().pretty()
187
+ ```
188
+
189
+ 3. **检查聚合查询**
190
+ ```javascript
191
+ // 测试聚合查询
192
+ db.plugin_stats.aggregate([
193
+ {
194
+ $group: {
195
+ _id: null,
196
+ total: { $sum: { $ifNull: ['$stats.activeUsers7d', 0] } }
197
+ }
198
+ }
199
+ ])
200
+ ```
201
+
202
+ ---
203
+
204
+ ## 问题:插件端上报失败
205
+
206
+ ### 错误现象
207
+
208
+ HydroOJ 日志显示:
209
+ ```
210
+ [TelemetryService] Report failed: 404 Not Found
211
+ ```
212
+
213
+ ### 解决方案
214
+
215
+ 1. **检查 REPORT_URL**
216
+ ```bash
217
+ grep REPORT_URL src/services/telemetryService.ts
218
+ ```
219
+
220
+ 2. **测试端点**
221
+ ```bash
222
+ curl -X POST https://your-vercel-app.vercel.app/api/report \
223
+ -H "Content-Type: application/json" \
224
+ -d '{
225
+ "instance_id": "test",
226
+ "event": "install",
227
+ "version": "1.8.0",
228
+ "installed_at": "2024-01-01T00:00:00Z",
229
+ "stats": {
230
+ "active_users_7d": 0,
231
+ "total_conversations": 0
232
+ },
233
+ "domain_hash": "test",
234
+ "timestamp": "2024-01-01T00:00:00Z"
235
+ }'
236
+ ```
237
+
238
+ 3. **检查 Vercel 部署状态**
239
+ - Vercel Dashboard → 项目 → Deployments
240
+ - 确认最新部署成功
241
+
242
+ ---
243
+
244
+ ## 问题:依赖安装警告
245
+
246
+ ### 错误现象
247
+
248
+ ```
249
+ npm warn deprecated rimraf@3.0.2
250
+ npm warn deprecated eslint@8.57.1
251
+ ```
252
+
253
+ ### 解决方案
254
+
255
+ 这些是警告,不是错误,不影响部署。可以忽略。
256
+
257
+ 如果想消除警告,可以更新依赖:
258
+
259
+ ```bash
260
+ npm update
261
+ npm audit fix
262
+ ```
263
+
264
+ ---
265
+
266
+ ## 验证部署成功
267
+
268
+ ### 1. 检查 Vercel 部署状态
269
+
270
+ 访问 Vercel Dashboard,确认:
271
+ - ✅ 部署状态:Ready
272
+ - ✅ 函数列表:显示 3 个函数(report, badge-installs, badge-active)
273
+
274
+ ### 2. 测试所有端点
275
+
276
+ ```bash
277
+ # 测试心跳端点
278
+ curl -X POST https://your-vercel-app.vercel.app/api/report \
279
+ -H "Content-Type: application/json" \
280
+ -d '{"instance_id":"test","event":"install","version":"1.8.0","installed_at":"2024-01-01T00:00:00Z","stats":{"active_users_7d":10,"total_conversations":50},"domain_hash":"test","timestamp":"2024-01-01T00:00:00Z"}'
281
+
282
+ # 预期:{"success": true}
283
+
284
+ # 测试安装数徽章
285
+ curl https://your-vercel-app.vercel.app/api/badge-installs
286
+
287
+ # 预期:{"schemaVersion":1,"label":"installations","message":"1","color":"blue"}
288
+
289
+ # 测试活跃用户徽章
290
+ curl https://your-vercel-app.vercel.app/api/badge-active
291
+
292
+ # 预期:{"schemaVersion":1,"label":"active users (7d)","message":"10","color":"green"}
293
+ ```
294
+
295
+ ### 3. 检查 MongoDB 数据
296
+
297
+ ```javascript
298
+ use hydro_ai_stats
299
+
300
+ // 查看数据
301
+ db.plugin_stats.find().pretty()
302
+
303
+ // 验证索引
304
+ db.plugin_stats.getIndexes()
305
+ ```
306
+
307
+ ### 4. 验证插件端
308
+
309
+ ```bash
310
+ # 查看 HydroOJ 日志
311
+ pm2 logs hydrooj | grep TelemetryService
312
+
313
+ # 预期输出
314
+ [TelemetryService] Initialized successfully
315
+ [TelemetryService] Report sent successfully (install)
316
+ ```
317
+
318
+ ---
319
+
320
+ ## 常见问题汇总
321
+
322
+ | 问题 | 原因 | 解决方案 |
323
+ |------|------|---------|
324
+ | 构建失败 | TypeScript 配置不兼容 | 添加 `buildCommand: null` |
325
+ | MongoDB 连接失败 | 环境变量或网络访问 | 检查 Vercel 环境变量和 Atlas 配置 |
326
+ | 函数超时 | 集群暂停或连接慢 | 唤醒集群,优化连接池 |
327
+ | 徽章 invalid | API 返回格式错误 | 检查 API 端点返回 |
328
+ | 徽章 error | 数据库查询失败 | 检查数据库连接和数据 |
329
+ | 上报失败 | URL 不正确 | 检查 REPORT_URL 配置 |
330
+
331
+ ---
332
+
333
+ ## 获取帮助
334
+
335
+ 如果以上方案都无法解决问题:
336
+
337
+ 1. **查看 Vercel 日志**
338
+ - Vercel Dashboard → 项目 → Functions → Logs
339
+ - 复制完整错误信息
340
+
341
+ 2. **查看 MongoDB Atlas 日志**
342
+ - MongoDB Atlas → Cluster → Metrics
343
+ - 检查连接数和操作数
344
+
345
+ 3. **提交 Issue**
346
+ - 访问 https://github.com/AltureT/hydro-ai-helper/issues
347
+ - 提供完整的错误日志和配置信息
348
+
349
+ ---
350
+
351
+ ## 相关文档
352
+
353
+ - [DEPLOYMENT_PHASE2.md](./DEPLOYMENT_PHASE2.md) - 详细部署指南
354
+ - [QUICKSTART.md](./QUICKSTART.md) - 快速开始指南
355
+ - [VERCEL_CONFIG.md](./VERCEL_CONFIG.md) - Vercel 配置指南
@@ -0,0 +1,88 @@
1
+ import type { VercelRequest, VercelResponse } from '@vercel/node';
2
+ import { getDb, PLUGIN_STATS_COLLECTION } from '../lib/mongodb';
3
+
4
+ const CORS_HEADERS: Record<string, string> = {
5
+ 'Access-Control-Allow-Origin': '*',
6
+ 'Access-Control-Allow-Methods': 'GET, OPTIONS',
7
+ 'Access-Control-Allow-Headers': 'Content-Type'
8
+ };
9
+
10
+ const compactFormatter = new Intl.NumberFormat('en', {
11
+ notation: 'compact',
12
+ compactDisplay: 'short',
13
+ maximumFractionDigits: 1
14
+ });
15
+
16
+ function applyCors(res: VercelResponse): void {
17
+ Object.entries(CORS_HEADERS).forEach(([key, value]) => {
18
+ res.setHeader(key, value);
19
+ });
20
+ }
21
+
22
+ function formatCount(value: number): string {
23
+ if (!Number.isFinite(value)) {
24
+ return '0';
25
+ }
26
+ return compactFormatter.format(value);
27
+ }
28
+
29
+ export default async function handler(
30
+ req: VercelRequest,
31
+ res: VercelResponse
32
+ ): Promise<void> {
33
+ applyCors(res);
34
+
35
+ if (req.method === 'OPTIONS') {
36
+ res.status(204).end();
37
+ return;
38
+ }
39
+
40
+ if (req.method !== 'GET') {
41
+ res.setHeader('Allow', 'GET, OPTIONS');
42
+ res.status(405).json({
43
+ schemaVersion: 1,
44
+ label: 'active users (7d)',
45
+ message: 'method not allowed',
46
+ color: 'lightgrey'
47
+ });
48
+ return;
49
+ }
50
+
51
+ try {
52
+ const db = await getDb();
53
+ const [result] = await db
54
+ .collection(PLUGIN_STATS_COLLECTION)
55
+ .aggregate([
56
+ {
57
+ $group: {
58
+ _id: null,
59
+ total: {
60
+ $sum: { $ifNull: ['$stats.activeUsers7d', 0] }
61
+ }
62
+ }
63
+ }
64
+ ])
65
+ .toArray();
66
+
67
+ const total =
68
+ result && typeof result.total === 'number' ? result.total : 0;
69
+
70
+ console.info('[badge-active] total', total);
71
+
72
+ res.setHeader('Cache-Control', 'public, max-age=0, s-maxage=300');
73
+ res.status(200).json({
74
+ schemaVersion: 1,
75
+ label: 'active users (7d)',
76
+ message: formatCount(total),
77
+ color: 'green'
78
+ });
79
+ } catch (error) {
80
+ console.error('[badge-active] error', error);
81
+ res.status(500).json({
82
+ schemaVersion: 1,
83
+ label: 'active users (7d)',
84
+ message: 'error',
85
+ color: 'lightgrey'
86
+ });
87
+ }
88
+ }
@@ -0,0 +1,75 @@
1
+ import type { VercelRequest, VercelResponse } from '@vercel/node';
2
+ import { getDb, PLUGIN_STATS_COLLECTION } from '../lib/mongodb';
3
+
4
+ const CORS_HEADERS: Record<string, string> = {
5
+ 'Access-Control-Allow-Origin': '*',
6
+ 'Access-Control-Allow-Methods': 'GET, OPTIONS',
7
+ 'Access-Control-Allow-Headers': 'Content-Type'
8
+ };
9
+
10
+ const compactFormatter = new Intl.NumberFormat('en', {
11
+ notation: 'compact',
12
+ compactDisplay: 'short',
13
+ maximumFractionDigits: 1
14
+ });
15
+
16
+ function applyCors(res: VercelResponse): void {
17
+ Object.entries(CORS_HEADERS).forEach(([key, value]) => {
18
+ res.setHeader(key, value);
19
+ });
20
+ }
21
+
22
+ function formatCount(value: number): string {
23
+ if (!Number.isFinite(value)) {
24
+ return '0';
25
+ }
26
+ return compactFormatter.format(value);
27
+ }
28
+
29
+ export default async function handler(
30
+ req: VercelRequest,
31
+ res: VercelResponse
32
+ ): Promise<void> {
33
+ applyCors(res);
34
+
35
+ if (req.method === 'OPTIONS') {
36
+ res.status(204).end();
37
+ return;
38
+ }
39
+
40
+ if (req.method !== 'GET') {
41
+ res.setHeader('Allow', 'GET, OPTIONS');
42
+ res.status(405).json({
43
+ schemaVersion: 1,
44
+ label: 'installations',
45
+ message: 'method not allowed',
46
+ color: 'lightgrey'
47
+ });
48
+ return;
49
+ }
50
+
51
+ try {
52
+ const db = await getDb();
53
+ const count = await db
54
+ .collection(PLUGIN_STATS_COLLECTION)
55
+ .countDocuments();
56
+
57
+ console.info('[badge-installs] count', count);
58
+
59
+ res.setHeader('Cache-Control', 'public, max-age=0, s-maxage=300');
60
+ res.status(200).json({
61
+ schemaVersion: 1,
62
+ label: 'installations',
63
+ message: formatCount(count),
64
+ color: 'blue'
65
+ });
66
+ } catch (error) {
67
+ console.error('[badge-installs] error', error);
68
+ res.status(500).json({
69
+ schemaVersion: 1,
70
+ label: 'installations',
71
+ message: 'error',
72
+ color: 'lightgrey'
73
+ });
74
+ }
75
+ }