@myassis/gateway 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 (65) hide show
  1. package/README.md +194 -0
  2. package/dist/.env +6 -0
  3. package/dist/api/index.js +182 -0
  4. package/dist/config/index.js +41 -0
  5. package/dist/index.js +183 -0
  6. package/dist/middleware/auth.js +53 -0
  7. package/dist/middleware/errorHandler.js +20 -0
  8. package/dist/routes/agent.js +513 -0
  9. package/dist/routes/auth.js +172 -0
  10. package/dist/routes/chat.js +45 -0
  11. package/dist/routes/config.js +21 -0
  12. package/dist/routes/models.js +123 -0
  13. package/dist/routes/service.js +240 -0
  14. package/dist/routes/settings.js +101 -0
  15. package/dist/routes/skillHub.js +126 -0
  16. package/dist/routes/skills.js +159 -0
  17. package/dist/routes/tasks.js +149 -0
  18. package/dist/routes/upload.js +129 -0
  19. package/dist/routes/version.js +66 -0
  20. package/dist/services/HMSPushService.js +24 -0
  21. package/dist/services/LocalTaskService.js +223 -0
  22. package/dist/services/NotificationService.js +242 -0
  23. package/dist/services/ServiceManager.js +348 -0
  24. package/dist/services/TaskSchedulerService.js +195 -0
  25. package/dist/services/TaskService.js +240 -0
  26. package/dist/services/WebSocketService.js +236 -0
  27. package/dist/services/agent/Agent.js +120 -0
  28. package/dist/services/agent/AgentManager.js +265 -0
  29. package/dist/services/agent/AgentStore.js +73 -0
  30. package/dist/services/dataService.js +293 -0
  31. package/dist/services/index.js +15 -0
  32. package/dist/services/llm/LLMClient.js +724 -0
  33. package/dist/services/memory/MemoryManager.js +117 -0
  34. package/dist/services/model/ModelCapabilities.js +141 -0
  35. package/dist/services/model/index.js +4 -0
  36. package/dist/services/models.js +16 -0
  37. package/dist/services/session/MigrationManager.js +176 -0
  38. package/dist/services/session/Session.js +733 -0
  39. package/dist/services/session/SessionManager.js +255 -0
  40. package/dist/services/session/SessionStore.js +186 -0
  41. package/dist/services/session/index.js +3 -0
  42. package/dist/services/skills.js +34 -0
  43. package/dist/services/systemPrompt.js +150 -0
  44. package/dist/services/task/PushTokenStore.js +124 -0
  45. package/dist/services/task/TaskStore.js +143 -0
  46. package/dist/services/tools/calculator.js +27 -0
  47. package/dist/services/tools/edit.js +318 -0
  48. package/dist/services/tools/exec.js +119 -0
  49. package/dist/services/tools/fetch.js +155 -0
  50. package/dist/services/tools/file.js +315 -0
  51. package/dist/services/tools/index.js +48 -0
  52. package/dist/services/tools/keyboard.js +145 -0
  53. package/dist/services/tools/model.js +86 -0
  54. package/dist/services/tools/mouse.js +55 -0
  55. package/dist/services/tools/screenshot.js +19 -0
  56. package/dist/services/tools/search.js +53 -0
  57. package/dist/services/tools/skill.js +108 -0
  58. package/dist/services/tools/task.js +110 -0
  59. package/dist/services/tools/types.js +1 -0
  60. package/dist/services/tools/webFetch.js +34 -0
  61. package/dist/stores/authStore.js +178 -0
  62. package/dist/stores/index.js +6 -0
  63. package/dist/stores/memoryStore.js +191 -0
  64. package/dist/stores/persistStore.js +317 -0
  65. package/package.json +94 -0
@@ -0,0 +1,45 @@
1
+ import { Router } from 'express';
2
+ import { getLogger } from '@pocketclaw/shared';
3
+ const logger = getLogger('ChatRoutes');
4
+ const router = Router();
5
+ // Chat completion proxy - forwards to cloud API
6
+ router.post('/completion', async (req, res) => {
7
+ try {
8
+ const { model, messages, stream = false } = req.body;
9
+ // TODO: Forward to actual cloud API based on model selection
10
+ // This is a placeholder implementation
11
+ res.json({
12
+ id: `chat-${Date.now()}`,
13
+ model,
14
+ choices: [{
15
+ message: {
16
+ role: 'assistant',
17
+ content: 'Gateway proxy ready. Configure cloud API credentials to use AI capabilities.'
18
+ }
19
+ }]
20
+ });
21
+ }
22
+ catch (error) {
23
+ logger.error('Chat completion error:', error);
24
+ res.status(500).json({ error: 'Chat completion failed' });
25
+ }
26
+ });
27
+ // SSE stream proxy
28
+ router.post('/stream', async (req, res) => {
29
+ try {
30
+ const { model, messages } = req.body;
31
+ // TODO: Forward to actual cloud API with SSE streaming
32
+ res.setHeader('Content-Type', 'text/event-stream');
33
+ res.setHeader('Cache-Control', 'no-cache');
34
+ res.setHeader('Connection', 'keep-alive');
35
+ // Placeholder response
36
+ res.write(`data: ${JSON.stringify({ content: 'Gateway stream ready. Configure cloud API credentials.' })}\n\n`);
37
+ res.write('data: [DONE]\n\n');
38
+ res.end();
39
+ }
40
+ catch (error) {
41
+ logger.error('Stream error:', error);
42
+ res.status(500).json({ error: 'Streaming failed' });
43
+ }
44
+ });
45
+ export default router;
@@ -0,0 +1,21 @@
1
+ import { Router } from 'express';
2
+ const router = Router();
3
+ // Get app configuration
4
+ router.get('/', (req, res) => {
5
+ res.json({
6
+ version: '2.0.0',
7
+ environment: process.env.NODE_ENV || 'development',
8
+ features: {
9
+ localModels: true,
10
+ cloudModels: true,
11
+ streaming: true,
12
+ fileSystem: true
13
+ },
14
+ limits: {
15
+ maxFileSize: 10 * 1024 * 1024, // 10MB
16
+ maxModels: 10,
17
+ maxSkills: 20
18
+ }
19
+ });
20
+ });
21
+ export default router;
@@ -0,0 +1,123 @@
1
+ import express from 'express';
2
+ import { getLogger, detectModelCapabilities } from '@pocketclaw/shared';
3
+ import { modelsService } from '../services/index.js';
4
+ import { persistStore } from '../stores/persistStore.js';
5
+ import { requireAuth } from '../middleware/auth.js';
6
+ const logger = getLogger('models');
7
+ const router = express.Router();
8
+ /**
9
+ * 获取所有模型配置
10
+ * GET /api/v1/models
11
+ */
12
+ router.get('/', async (req, res) => {
13
+ try {
14
+ const result = await modelsService.list();
15
+ res.json(result);
16
+ }
17
+ catch (error) {
18
+ logger.error('获取模型列表失败:', error?.message);
19
+ res.status(500).json({ success: false, error: '获取模型列表失败' });
20
+ }
21
+ });
22
+ /**
23
+ * 获取单个模型配置
24
+ * GET /api/v1/models/:id
25
+ */
26
+ router.get('/:id', async (req, res) => {
27
+ try {
28
+ const result = await modelsService.get(req.params.id);
29
+ res.json(result);
30
+ }
31
+ catch (error) {
32
+ logger.error('获取模型详情失败:', error?.message);
33
+ res.status(500).json({ success: false, error: '获取模型详情失败' });
34
+ }
35
+ });
36
+ /**
37
+ * 创建模型配置
38
+ * POST /api/v1/models
39
+ */
40
+ router.post('/', requireAuth, async (req, res) => {
41
+ try {
42
+ const userId = req.userId;
43
+ const modelData = req.body;
44
+ const apiKey = modelData.apiKey;
45
+ // 自动检测模型能力,设置 supportsToolCall
46
+ const modelCapabilities = detectModelCapabilities(modelData.modelId || '');
47
+ const dataToSave = {
48
+ ...modelData,
49
+ // 数据库 is_primary, is_active, supports_tool_call 都是 INTEGER 类型,转换为 0/1
50
+ isPrimary: modelData.isPrimary ? 1 : 0,
51
+ isActive: modelData.isActive ? 1 : 0,
52
+ supportsToolCall: modelCapabilities.supportsToolCall ? 1 : 0,
53
+ };
54
+ // 调用服务端,不包含 apiKey
55
+ const result = await modelsService.create(dataToSave);
56
+ // 如果提供了 API Key,持久化存储
57
+ if (apiKey && result.success && result.data?.id) {
58
+ persistStore.setModelApiKey(result.data.id, apiKey);
59
+ }
60
+ res.json(result);
61
+ }
62
+ catch (error) {
63
+ logger.error('创建模型失败:', error?.message);
64
+ res.status(500).json({ success: false, error: '创建模型失败' });
65
+ }
66
+ });
67
+ /**
68
+ * 更新模型配置
69
+ * PUT /api/v1/models/:id
70
+ */
71
+ router.put('/:id', requireAuth, async (req, res) => {
72
+ try {
73
+ const userId = req.userId;
74
+ const modelData = req.body;
75
+ const apiKey = modelData.apiKey;
76
+ // API Key 单独存储到本地,不传给服务端
77
+ if (apiKey) {
78
+ persistStore.setModelApiKey(req.params.id, apiKey);
79
+ }
80
+ // 自动检测模型能力,设置 supportsToolCall
81
+ const modelCapabilities = detectModelCapabilities(modelData.modelId || '');
82
+ const dataToSave = {
83
+ ...modelData,
84
+ supportsToolCall: modelCapabilities.supportsToolCall ? 1 : 0,
85
+ };
86
+ // 调用服务端,不包含 apiKey
87
+ const result = await modelsService.update(req.params.id, dataToSave);
88
+ res.json(result);
89
+ }
90
+ catch (error) {
91
+ logger.error('更新模型失败:', error?.message);
92
+ res.status(500).json({ success: false, error: '更新模型失败' });
93
+ }
94
+ });
95
+ /**
96
+ * 删除模型配置
97
+ * DELETE /api/v1/models/:id
98
+ */
99
+ router.delete('/:id', async (req, res) => {
100
+ try {
101
+ const result = await modelsService.delete(req.params.id);
102
+ res.json(result);
103
+ }
104
+ catch (error) {
105
+ logger.error('删除模型失败:', error?.message);
106
+ res.status(500).json({ success: false, error: '删除模型失败' });
107
+ }
108
+ });
109
+ /**
110
+ * 设置主模型
111
+ * PUT /api/v1/models/:id/primary
112
+ */
113
+ router.post('/:id/primary', async (req, res) => {
114
+ try {
115
+ const result = await modelsService.setPrimary(req.params.id);
116
+ res.json(result);
117
+ }
118
+ catch (error) {
119
+ logger.error('设置主模型失败:', error?.message);
120
+ res.status(500).json({ success: false, error: '设置主模型失败' });
121
+ }
122
+ });
123
+ export default router;
@@ -0,0 +1,240 @@
1
+ import { Router } from 'express';
2
+ import { getLogger } from '@pocketclaw/shared';
3
+ import path from 'path';
4
+ import { getServiceInfo, installService, uninstallService, startService, stopService, restartService, checkForUpdates, updateService, } from '../services/ServiceManager.js';
5
+ import { webSocketService } from '../services/WebSocketService.js';
6
+ import { requireAuth } from '../middleware/auth.js';
7
+ const router = Router();
8
+ // 所有 service 路由需要认证
9
+ router.use(requireAuth);
10
+ const logger = getLogger('service');
11
+ /**
12
+ * 获取服务状态
13
+ * GET /api/v1/service
14
+ */
15
+ router.get('/', async (_req, res) => {
16
+ try {
17
+ const info = await getServiceInfo();
18
+ res.json({ success: true, data: info });
19
+ }
20
+ catch (err) {
21
+ logger.error('获取服务状态失败:', err?.message);
22
+ res.status(500).json({ success: false, error: err?.message });
23
+ }
24
+ });
25
+ /**
26
+ * 安装并启动服务
27
+ * POST /api/v1/service/install
28
+ */
29
+ router.post('/install', async (_req, res) => {
30
+ try {
31
+ const result = await installService();
32
+ if (result.success) {
33
+ res.json(result);
34
+ }
35
+ else {
36
+ res.status(400).json(result);
37
+ }
38
+ }
39
+ catch (err) {
40
+ logger.error('安装服务失败:', err?.message);
41
+ res.status(500).json({ success: false, message: err?.message });
42
+ }
43
+ });
44
+ /**
45
+ * 卸载服务
46
+ * DELETE /api/v1/service
47
+ */
48
+ router.delete('/', async (_req, res) => {
49
+ try {
50
+ const result = await uninstallService();
51
+ if (result.success) {
52
+ res.json(result);
53
+ }
54
+ else {
55
+ res.status(400).json(result);
56
+ }
57
+ }
58
+ catch (err) {
59
+ logger.error('卸载服务失败:', err?.message);
60
+ res.status(500).json({ success: false, message: err?.message });
61
+ }
62
+ });
63
+ /**
64
+ * 启动服务
65
+ * POST /api/v1/service/start
66
+ */
67
+ router.post('/start', async (_req, res) => {
68
+ try {
69
+ const result = await startService();
70
+ if (result.success) {
71
+ res.json(result);
72
+ }
73
+ else {
74
+ res.status(400).json(result);
75
+ }
76
+ }
77
+ catch (err) {
78
+ logger.error('启动服务失败:', err?.message);
79
+ res.status(500).json({ success: false, message: err?.message });
80
+ }
81
+ });
82
+ /**
83
+ * 停止服务
84
+ * POST /api/v1/service/stop
85
+ */
86
+ router.post('/stop', async (_req, res) => {
87
+ try {
88
+ const result = await stopService();
89
+ if (result.success) {
90
+ res.json(result);
91
+ }
92
+ else {
93
+ res.status(400).json(result);
94
+ }
95
+ }
96
+ catch (err) {
97
+ logger.error('停止服务失败:', err?.message);
98
+ res.status(500).json({ success: false, message: err?.message });
99
+ }
100
+ });
101
+ /**
102
+ * 重启服务
103
+ * POST /api/v1/service/restart
104
+ */
105
+ router.post('/restart', async (_req, res) => {
106
+ try {
107
+ const result = await restartService();
108
+ if (result.success) {
109
+ res.json(result);
110
+ }
111
+ else {
112
+ res.status(400).json(result);
113
+ }
114
+ }
115
+ catch (err) {
116
+ logger.error('重启服务失败:', err?.message);
117
+ res.status(500).json({ success: false, message: err?.message });
118
+ }
119
+ });
120
+ /**
121
+ * 检查是否有可用更新
122
+ * GET /api/v1/service/check-update
123
+ */
124
+ router.get('/check-update', async (_req, res) => {
125
+ try {
126
+ const result = await checkForUpdates();
127
+ res.json({ success: true, data: result });
128
+ }
129
+ catch (err) {
130
+ logger.error('检查更新失败:', err?.message);
131
+ res.status(500).json({ success: false, message: err?.message });
132
+ }
133
+ });
134
+ /**
135
+ * 检查并更新服务
136
+ * POST /api/v1/service/update
137
+ */
138
+ router.post('/update', async (_req, res) => {
139
+ try {
140
+ // 先检查版本
141
+ const check = await checkForUpdates();
142
+ if (check.error) {
143
+ res.status(500).json({ success: false, message: `检查更新失败: ${check.error}` });
144
+ return;
145
+ }
146
+ if (!check.hasUpdate) {
147
+ res.json({ success: true, message: `已是最新版本 ${check.currentVersion}` });
148
+ return;
149
+ }
150
+ // 尝试自动更新(有 nssm 时会成功)
151
+ const result = await updateService();
152
+ if (result.success) {
153
+ res.json(result);
154
+ return;
155
+ }
156
+ // 自动更新失败,推送手动更新提示给 Desktop
157
+ const userId = 'default';
158
+ const pushed = webSocketService.sendUpdateRequired(userId, result.message);
159
+ if (pushed) {
160
+ res.json({ success: true, message: '已推送手动更新提示到桌面端', manual: true });
161
+ }
162
+ else {
163
+ // Desktop 不在线时直接返回错误消息
164
+ res.status(400).json({ success: false, message: result.message, manual: true });
165
+ }
166
+ }
167
+ catch (err) {
168
+ logger.error('更新服务失败:', err?.message);
169
+ res.status(500).json({ success: false, message: err?.message });
170
+ }
171
+ });
172
+ /**
173
+ * 获取最新 Gateway EXE 下载地址
174
+ * GET /api/v1/service/latest-exe-url
175
+ */
176
+ router.get('/latest-exe-url', async (_req, res) => {
177
+ try {
178
+ const check = await checkForUpdates();
179
+ if (check.error) {
180
+ res.status(500).json({ success: false, message: check.error });
181
+ return;
182
+ }
183
+ if (!check.hasUpdate) {
184
+ res.json({ success: true, hasUpdate: false, message: `已是最新版本 ${check.currentVersion}` });
185
+ return;
186
+ }
187
+ // TODO: 从 GitHub Releases 或自有 CDN 获取下载地址
188
+ // 示例: https://github.com/pocketclaw/gateway/releases/download/v${check.latestVersion}/gateway.exe
189
+ const downloadUrl = `https://pocket-claw-1251246038.cos.ap-chengdu.myqcloud.com/pocket-claw-1251246038/installs/v${check.latestVersion}/gateway-installer_${check.latestVersion}.exe`;
190
+ res.json({
191
+ success: true,
192
+ hasUpdate: true,
193
+ currentVersion: check.currentVersion,
194
+ latestVersion: check.latestVersion,
195
+ downloadUrl,
196
+ });
197
+ }
198
+ catch (err) {
199
+ logger.error('获取下载地址失败:', err?.message);
200
+ res.status(500).json({ success: false, message: err?.message });
201
+ }
202
+ });
203
+ /**
204
+ * EXE 方式更新 Gateway
205
+ * POST /api/v1/service/update-exe
206
+ * 由 Desktop 调用,Gateway 下载最新 exe 并替换自己
207
+ */
208
+ router.post('/update-exe', async (_req, res) => {
209
+ try {
210
+ // 检查是否是 exe 运行方式
211
+ if (!process.pkg) {
212
+ res.status(400).json({ success: false, message: '当前不是 EXE 运行方式,无法使用此接口' });
213
+ return;
214
+ }
215
+ // 获取最新版本信息
216
+ const check = await checkForUpdates();
217
+ if (check.error) {
218
+ res.status(500).json({ success: false, message: `检查更新失败: ${check.error}` });
219
+ return;
220
+ }
221
+ if (!check.hasUpdate) {
222
+ res.json({ success: true, message: `已是最新版本 ${check.currentVersion}` });
223
+ return;
224
+ }
225
+ // 获取当前 exe 路径
226
+ const currentExePath = process.execPath;
227
+ const exeDir = path.dirname(currentExePath);
228
+ const exeName = path.basename(currentExePath);
229
+ const backupPath = path.join(exeDir, `${exeName}.backup`);
230
+ const downloadPath = path.join(exeDir, `${exeName}.new`);
231
+ // TODO: 从 GitHub Releases 或自有 CDN 下载最新 exe
232
+ // 这里需要实现下载逻辑
233
+ res.status(501).json({ success: false, message: 'EXE 更新功能尚未实现' });
234
+ }
235
+ catch (err) {
236
+ logger.error('EXE 更新失败:', err?.message);
237
+ res.status(500).json({ success: false, message: err?.message });
238
+ }
239
+ });
240
+ export default router;
@@ -0,0 +1,101 @@
1
+ import express from 'express';
2
+ import { getLogger } from '@pocketclaw/shared';
3
+ import { settingsService } from '../services/index.js';
4
+ import { authStore, persistStore } from '../stores/index.js';
5
+ import { authApi } from '../api/index.js';
6
+ const logger = getLogger('settings');
7
+ const router = express.Router();
8
+ /**
9
+ * 获取用户设置
10
+ * GET /api/v1/settings
11
+ */
12
+ router.get('/', async (req, res) => {
13
+ try {
14
+ const result = await settingsService.get();
15
+ res.json(result);
16
+ }
17
+ catch (error) {
18
+ logger.error('获取用户设置失败:', error?.message);
19
+ res.status(500).json({ success: false, error: '获取用户设置失败' });
20
+ }
21
+ });
22
+ /**
23
+ * 更新用户设置
24
+ * PUT /api/v1/settings
25
+ */
26
+ router.put('/', async (req, res) => {
27
+ try {
28
+ const result = await settingsService.update(req.body);
29
+ res.json(result);
30
+ }
31
+ catch (error) {
32
+ logger.error('更新用户设置失败:', error?.message);
33
+ res.status(500).json({ success: false, error: '更新用户设置失败' });
34
+ }
35
+ });
36
+ /**
37
+ * 修改密码
38
+ * PUT /api/v1/settings/password
39
+ */
40
+ router.put('/password', async (req, res) => {
41
+ try {
42
+ const { oldPassword, newPassword } = req.body;
43
+ if (!oldPassword || !newPassword) {
44
+ res.status(400).json({ success: false, error: 'Missing required fields' });
45
+ return;
46
+ }
47
+ const result = await authApi.changePassword({ oldPassword, newPassword });
48
+ res.json(result);
49
+ }
50
+ catch (error) {
51
+ logger.error('修改密码失败:', error?.message);
52
+ res.status(500).json({ success: false, error: '修改密码失败' });
53
+ }
54
+ });
55
+ /**
56
+ * 获取本地用户设置(不上传服务端)
57
+ * GET /api/v1/settings/local
58
+ */
59
+ router.get('/local', async (req, res) => {
60
+ try {
61
+ const settings = persistStore.getAllLocalUserSettings();
62
+ res.json({ success: true, data: settings });
63
+ }
64
+ catch (error) {
65
+ logger.error('获取本地用户设置失败:', error?.message);
66
+ res.status(500).json({ success: false, error: '获取本地用户设置失败' });
67
+ }
68
+ });
69
+ /**
70
+ * 更新本地用户设置(不上传服务端)
71
+ * PUT /api/v1/settings/local
72
+ */
73
+ router.put('/local', async (req, res) => {
74
+ try {
75
+ persistStore.setLocalUserSettings(req.body);
76
+ const settings = persistStore.getAllLocalUserSettings();
77
+ res.json({ success: true, data: settings });
78
+ }
79
+ catch (error) {
80
+ logger.error('更新本地用户设置失败:', error?.message);
81
+ res.status(500).json({ success: false, error: '更新本地用户设置失败' });
82
+ }
83
+ });
84
+ /**
85
+ * 删除账号
86
+ * DELETE /api/v1/settings/account
87
+ */
88
+ router.delete('/account', async (req, res) => {
89
+ try {
90
+ const result = await authApi.deleteAccount({ password: req.params.password });
91
+ if (result.success) {
92
+ authStore.clear();
93
+ }
94
+ res.json(result);
95
+ }
96
+ catch (error) {
97
+ logger.error('删除账号失败:', error?.message);
98
+ res.status(500).json({ success: false, error: '删除账号失败' });
99
+ }
100
+ });
101
+ export default router;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Gateway SkillHub 路由
3
+ * 技能库(可安装的技能)
4
+ */
5
+ import express from 'express';
6
+ import { skillHubService } from '../services/index.js';
7
+ import { requireAuth } from '../middleware/auth.js';
8
+ import { getLogger } from '@pocketclaw/shared';
9
+ const logger = getLogger('skillHub');
10
+ const router = express.Router();
11
+ /**
12
+ * 获取技能库列表
13
+ * GET /api/v1/skill-hub?page=1&pageSize=20&keyword=xxx&category=xxx
14
+ */
15
+ router.get('/', async (req, res) => {
16
+ try {
17
+ const { page, pageSize, search, category } = req.query;
18
+ const params = {};
19
+ if (page)
20
+ params.page = parseInt(page);
21
+ if (pageSize)
22
+ params.pageSize = parseInt(pageSize);
23
+ if (search)
24
+ params.keyword = search;
25
+ if (category)
26
+ params.category = category;
27
+ const result = await skillHubService.list(params);
28
+ res.json(result);
29
+ }
30
+ catch (error) {
31
+ logger.debug('获取技能库列表失败:', error?.message);
32
+ res.status(500).json({ success: false, error: error?.message });
33
+ }
34
+ });
35
+ /**
36
+ * 获取技能库详情
37
+ * GET /api/v1/skill-hub/:id
38
+ */
39
+ router.get('/:id', async (req, res) => {
40
+ try {
41
+ const hubId = req.params.id;
42
+ if (!hubId) {
43
+ return res.status(400).json({ success: false, error: '无效的技能库ID' });
44
+ }
45
+ const result = await skillHubService.get(hubId);
46
+ res.json(result);
47
+ }
48
+ catch (error) {
49
+ logger.error('获取技能库详情失败:', error?.message);
50
+ res.status(500).json({ success: false, error: '获取技能库详情失败' });
51
+ }
52
+ });
53
+ /**
54
+ * 获取用户是否使用过技能
55
+ * GET /api/v1/skill-hub/:id/used
56
+ */
57
+ router.get('/:id/used', async (req, res) => {
58
+ try {
59
+ const hubId = req.params.id;
60
+ if (!hubId) {
61
+ return res.status(400).json({ success: false, error: '无效的技能库ID' });
62
+ }
63
+ const result = await skillHubService.checkUsed(hubId);
64
+ res.json(result);
65
+ }
66
+ catch (error) {
67
+ logger.error('检查技能使用状态失败:', error?.message);
68
+ res.status(500).json({ success: false, error: error?.message });
69
+ }
70
+ });
71
+ /**
72
+ * 获取用户评分
73
+ * GET /api/v1/skill-hub/:id/user-rating
74
+ */
75
+ router.get('/:id/user-rating', async (req, res) => {
76
+ try {
77
+ const hubId = req.params.id;
78
+ if (!hubId) {
79
+ return res.status(400).json({ success: false, error: '无效的技能库ID' });
80
+ }
81
+ const result = await skillHubService.userRating(hubId);
82
+ res.json(result);
83
+ }
84
+ catch (error) {
85
+ logger.error('获取用户评分失败:', error?.message);
86
+ res.status(500).json({ success: false, error: error?.message });
87
+ }
88
+ });
89
+ /**
90
+ * 获取技能分类
91
+ * GET /api/v1/skill-hub/categories
92
+ */
93
+ router.get('/categories/list', async (req, res) => {
94
+ try {
95
+ const result = await skillHubService.categories();
96
+ res.json(result);
97
+ }
98
+ catch (error) {
99
+ logger.error('获取技能分类失败:', error?.message);
100
+ res.status(500).json({ success: false, error: error?.message });
101
+ }
102
+ });
103
+ /**
104
+ * 技能库评分
105
+ * POST /api/v1/skill-hub/:id/rate
106
+ */
107
+ router.post('/:id/rate', requireAuth, async (req, res) => {
108
+ try {
109
+ const hubId = req.params.id;
110
+ const { rating } = req.body;
111
+ const userId = req.userId;
112
+ if (!hubId) {
113
+ return res.status(400).json({ success: false, error: '无效的技能库ID' });
114
+ }
115
+ if (rating === undefined || rating < 1 || rating > 5) {
116
+ return res.status(400).json({ success: false, error: '评分必须是1-5的数字' });
117
+ }
118
+ const result = await skillHubService.rate(hubId, rating);
119
+ res.json(result);
120
+ }
121
+ catch (error) {
122
+ logger.error('技能库评分失败:', error?.message);
123
+ res.status(500).json({ success: false, error: error?.message });
124
+ }
125
+ });
126
+ export default router;