skill-base 2.0.12 → 2.0.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skill-base",
3
- "version": "2.0.12",
3
+ "version": "2.0.14",
4
4
  "description": "Skill Base - 私有部署的轻量级 Skill 管理平台",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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();
@@ -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(`
@@ -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
 
@@ -90,7 +90,8 @@ async function publishRoutes(fastify, options) {
90
90
  version,
91
91
  changelog || '',
92
92
  zipRelativePath,
93
- request.user.id
93
+ request.user.id,
94
+ description || ''
94
95
  );
95
96
 
96
97
  // 更新 skill 的最新版本
@@ -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
- return {
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;