skill-base 2.0.11 → 2.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/package.json +2 -2
- package/src/database.js +1 -0
- package/src/models/skill.js +24 -0
- package/src/models/version.js +14 -4
- package/src/routes/publish.js +2 -1
- package/src/routes/skills.js +97 -6
- package/static/assets/index-BTXGUfWd.js +209 -0
- package/static/assets/index-BYG48306.css +1 -0
- package/static/index.html +2 -2
- package/static/assets/index-CxLQPZK_.css +0 -1
- package/static/assets/index-DLXcqiPx.js +0 -209
package/README.md
CHANGED
|
@@ -87,6 +87,10 @@ npx skill-base --base-path /skills/ # 部署在子路径下 (例如: /skills/)
|
|
|
87
87
|
| `--host` | `-h` | 指定监听地址 | 0.0.0.0 |
|
|
88
88
|
| `--data-dir` | `-d` | 指定数据目录 | 包内 data/ |
|
|
89
89
|
| `--base-path` | - | 指定部署前缀 | / |
|
|
90
|
+
| `--no-cappy` | - | 禁用 Cappy 水豚吉祥物 | 启用 |
|
|
91
|
+
| `--verbose` | `-v` | 启用调试日志 | 禁用 |
|
|
92
|
+
| `--help` | - | 显示帮助信息 | - |
|
|
93
|
+
| `--version` | - | 显示版本号 | - |
|
|
90
94
|
|
|
91
95
|
> 🔐 **首次运行须知**:系统启动后,首次访问 Web 端将自动跳转至**初始化页面**,请根据提示设置系统管理员账号与密码。
|
|
92
96
|
|
package/package.json
CHANGED
package/src/database.js
CHANGED
|
@@ -116,6 +116,7 @@ try {
|
|
|
116
116
|
} catch(e) {}
|
|
117
117
|
try { db.exec("ALTER TABLE users ADD COLUMN created_by INTEGER REFERENCES users(id)"); } catch(e) {}
|
|
118
118
|
try { db.exec("ALTER TABLE users ADD COLUMN name TEXT"); } catch(e) {}
|
|
119
|
+
try { db.exec("ALTER TABLE skill_versions ADD COLUMN description TEXT"); } catch(e) {}
|
|
119
120
|
|
|
120
121
|
// 数据迁移:为已有 Skills 的 owner 插入 skill_collaborators 记录
|
|
121
122
|
const existingSkills = db.prepare('SELECT id, owner_id FROM skills').all();
|
package/src/models/skill.js
CHANGED
|
@@ -40,6 +40,30 @@ const SkillModel = {
|
|
|
40
40
|
return this.findById(id);
|
|
41
41
|
},
|
|
42
42
|
|
|
43
|
+
// 更新 Skill
|
|
44
|
+
update(id, name, description) {
|
|
45
|
+
const fields = [];
|
|
46
|
+
const values = [];
|
|
47
|
+
if (name !== undefined) {
|
|
48
|
+
fields.push('name = ?');
|
|
49
|
+
values.push(name);
|
|
50
|
+
}
|
|
51
|
+
if (description !== undefined) {
|
|
52
|
+
fields.push('description = ?');
|
|
53
|
+
values.push(description);
|
|
54
|
+
}
|
|
55
|
+
if (fields.length === 0) return this.findById(id);
|
|
56
|
+
|
|
57
|
+
fields.push('updated_at = CURRENT_TIMESTAMP');
|
|
58
|
+
values.push(id);
|
|
59
|
+
|
|
60
|
+
db.prepare(`
|
|
61
|
+
UPDATE skills SET ${fields.join(', ')} WHERE id = ?
|
|
62
|
+
`).run(...values);
|
|
63
|
+
|
|
64
|
+
return this.findById(id);
|
|
65
|
+
},
|
|
66
|
+
|
|
43
67
|
// 更新 latest_version 和 updated_at
|
|
44
68
|
updateLatestVersion(id, version) {
|
|
45
69
|
db.prepare(`
|
package/src/models/version.js
CHANGED
|
@@ -2,11 +2,11 @@ const db = require('../database');
|
|
|
2
2
|
|
|
3
3
|
const VersionModel = {
|
|
4
4
|
// 创建新版本
|
|
5
|
-
create(skillId, version, changelog, zipPath, uploaderId) {
|
|
5
|
+
create(skillId, version, changelog, zipPath, uploaderId, description) {
|
|
6
6
|
const result = db.prepare(`
|
|
7
|
-
INSERT INTO skill_versions (skill_id, version, changelog, zip_path, uploader_id)
|
|
8
|
-
VALUES (?, ?, ?, ?, ?)
|
|
9
|
-
`).run(skillId, version, changelog || '', zipPath, uploaderId);
|
|
7
|
+
INSERT INTO skill_versions (skill_id, version, changelog, zip_path, uploader_id, description)
|
|
8
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
9
|
+
`).run(skillId, version, changelog || '', zipPath, uploaderId, description || '');
|
|
10
10
|
return this.findById(result.lastInsertRowid);
|
|
11
11
|
},
|
|
12
12
|
|
|
@@ -51,6 +51,16 @@ const VersionModel = {
|
|
|
51
51
|
ORDER BY sv.created_at DESC
|
|
52
52
|
LIMIT 1
|
|
53
53
|
`).get(skillId);
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// 更新版本描述和更新日志
|
|
57
|
+
update(id, description, changelog) {
|
|
58
|
+
db.prepare(`
|
|
59
|
+
UPDATE skill_versions
|
|
60
|
+
SET description = ?, changelog = ?
|
|
61
|
+
WHERE id = ?
|
|
62
|
+
`).run(description, changelog, id);
|
|
63
|
+
return this.findById(id);
|
|
54
64
|
}
|
|
55
65
|
};
|
|
56
66
|
|
package/src/routes/publish.js
CHANGED
package/src/routes/skills.js
CHANGED
|
@@ -3,11 +3,12 @@ const path = require('path');
|
|
|
3
3
|
const SkillModel = require('../models/skill');
|
|
4
4
|
const VersionModel = require('../models/version');
|
|
5
5
|
const { getZipPath, resolveZipPath } = require('../utils/zip');
|
|
6
|
+
const { canManageSkill } = require('../utils/permission');
|
|
6
7
|
|
|
7
8
|
// 格式化 skill,将 owner 转为对象
|
|
8
|
-
function formatSkill(skill) {
|
|
9
|
+
function formatSkill(skill, currentUser) {
|
|
9
10
|
if (!skill) return null;
|
|
10
|
-
|
|
11
|
+
const result = {
|
|
11
12
|
id: skill.id,
|
|
12
13
|
name: skill.name,
|
|
13
14
|
description: skill.description,
|
|
@@ -20,6 +21,24 @@ function formatSkill(skill) {
|
|
|
20
21
|
created_at: skill.created_at,
|
|
21
22
|
updated_at: skill.updated_at
|
|
22
23
|
};
|
|
24
|
+
|
|
25
|
+
if (currentUser) {
|
|
26
|
+
if (currentUser.id === skill.owner_id) {
|
|
27
|
+
result.permission = 'owner';
|
|
28
|
+
} else {
|
|
29
|
+
const db = require('../database');
|
|
30
|
+
const collab = db.prepare('SELECT role FROM skill_collaborators WHERE skill_id = ? AND user_id = ?').get(skill.id, currentUser.id);
|
|
31
|
+
if (collab) {
|
|
32
|
+
result.permission = collab.role;
|
|
33
|
+
} else {
|
|
34
|
+
result.permission = 'user';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
result.permission = 'user';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return result;
|
|
23
42
|
}
|
|
24
43
|
|
|
25
44
|
// 格式化 version,将 uploader 转为对象
|
|
@@ -30,6 +49,7 @@ function formatVersion(version) {
|
|
|
30
49
|
skill_id: version.skill_id,
|
|
31
50
|
version: version.version,
|
|
32
51
|
changelog: version.changelog,
|
|
52
|
+
description: version.description,
|
|
33
53
|
zip_path: version.zip_path,
|
|
34
54
|
uploader: {
|
|
35
55
|
id: version.uploader_id,
|
|
@@ -42,10 +62,10 @@ function formatVersion(version) {
|
|
|
42
62
|
|
|
43
63
|
async function skillsRoutes(fastify, options) {
|
|
44
64
|
// GET / - 获取 skills 列表
|
|
45
|
-
fastify.get('/', async (request, reply) => {
|
|
65
|
+
fastify.get('/', { preHandler: [fastify.optionalAuth] }, async (request, reply) => {
|
|
46
66
|
const { q } = request.query;
|
|
47
67
|
const skills = SkillModel.search(q);
|
|
48
|
-
const formattedSkills = skills.map(formatSkill);
|
|
68
|
+
const formattedSkills = skills.map(skill => formatSkill(skill, request.user));
|
|
49
69
|
|
|
50
70
|
return {
|
|
51
71
|
skills: formattedSkills,
|
|
@@ -54,7 +74,7 @@ async function skillsRoutes(fastify, options) {
|
|
|
54
74
|
});
|
|
55
75
|
|
|
56
76
|
// GET /:skill_id - 获取单个 skill
|
|
57
|
-
fastify.get('/:skill_id', async (request, reply) => {
|
|
77
|
+
fastify.get('/:skill_id', { preHandler: [fastify.optionalAuth] }, async (request, reply) => {
|
|
58
78
|
const { skill_id } = request.params;
|
|
59
79
|
const skill = SkillModel.findById(skill_id);
|
|
60
80
|
|
|
@@ -62,7 +82,7 @@ async function skillsRoutes(fastify, options) {
|
|
|
62
82
|
return reply.code(404).send({ detail: 'Skill not found' });
|
|
63
83
|
}
|
|
64
84
|
|
|
65
|
-
return formatSkill(skill);
|
|
85
|
+
return formatSkill(skill, request.user);
|
|
66
86
|
});
|
|
67
87
|
|
|
68
88
|
// GET /:skill_id/versions - 获取 skill 的所有版本
|
|
@@ -119,6 +139,77 @@ async function skillsRoutes(fastify, options) {
|
|
|
119
139
|
|
|
120
140
|
return fs.createReadStream(finalZipPath);
|
|
121
141
|
});
|
|
142
|
+
|
|
143
|
+
// PUT /:skill_id - 更新 skill 的基本信息
|
|
144
|
+
fastify.put('/:skill_id', {
|
|
145
|
+
preHandler: [fastify.authenticate]
|
|
146
|
+
}, async (request, reply) => {
|
|
147
|
+
const { skill_id } = request.params;
|
|
148
|
+
const { name, description } = request.body || {};
|
|
149
|
+
|
|
150
|
+
if (!SkillModel.exists(skill_id)) {
|
|
151
|
+
return reply.code(404).send({ detail: 'Skill not found' });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!canManageSkill(request.user, skill_id)) {
|
|
155
|
+
return reply.code(403).send({ ok: false, error: 'forbidden', detail: 'Owner or admin permission required' });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const updated = SkillModel.update(skill_id, name, description);
|
|
159
|
+
return formatSkill(updated, request.user);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// PUT /:skill_id/head - 设置 skill 的最新版本 (Head 指针)
|
|
163
|
+
fastify.put('/:skill_id/head', {
|
|
164
|
+
preHandler: [fastify.authenticate]
|
|
165
|
+
}, async (request, reply) => {
|
|
166
|
+
const { skill_id } = request.params;
|
|
167
|
+
const { version } = request.body || {};
|
|
168
|
+
|
|
169
|
+
if (!version) {
|
|
170
|
+
return reply.code(400).send({ detail: 'Version is required' });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!SkillModel.exists(skill_id)) {
|
|
174
|
+
return reply.code(404).send({ detail: 'Skill not found' });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!canManageSkill(request.user, skill_id)) {
|
|
178
|
+
return reply.code(403).send({ ok: false, error: 'forbidden', detail: 'Owner or admin permission required' });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const versionRecord = VersionModel.findByVersion(skill_id, version);
|
|
182
|
+
if (!versionRecord) {
|
|
183
|
+
return reply.code(404).send({ detail: 'Version not found' });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
SkillModel.updateLatestVersion(skill_id, version);
|
|
187
|
+
return { ok: true, skill_id, latest_version: version };
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// PATCH /:skill_id/versions/:version - 更新指定版本的介绍和日志
|
|
191
|
+
fastify.patch('/:skill_id/versions/:version', {
|
|
192
|
+
preHandler: [fastify.authenticate]
|
|
193
|
+
}, async (request, reply) => {
|
|
194
|
+
const { skill_id, version } = request.params;
|
|
195
|
+
const { description, changelog } = request.body || {};
|
|
196
|
+
|
|
197
|
+
if (!SkillModel.exists(skill_id)) {
|
|
198
|
+
return reply.code(404).send({ detail: 'Skill not found' });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!canManageSkill(request.user, skill_id)) {
|
|
202
|
+
return reply.code(403).send({ ok: false, error: 'forbidden', detail: 'Owner or admin permission required' });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const versionRecord = VersionModel.findByVersion(skill_id, version);
|
|
206
|
+
if (!versionRecord) {
|
|
207
|
+
return reply.code(404).send({ detail: 'Version not found' });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const updated = VersionModel.update(versionRecord.id, description, changelog);
|
|
211
|
+
return formatVersion(updated);
|
|
212
|
+
});
|
|
122
213
|
}
|
|
123
214
|
|
|
124
215
|
module.exports = skillsRoutes;
|