@open-skills-hub/api 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.
- package/dist/controllers/audit.d.ts +33 -0
- package/dist/controllers/audit.d.ts.map +1 -0
- package/dist/controllers/audit.js +122 -0
- package/dist/controllers/audit.js.map +1 -0
- package/dist/controllers/cache.d.ts +42 -0
- package/dist/controllers/cache.d.ts.map +1 -0
- package/dist/controllers/cache.js +247 -0
- package/dist/controllers/cache.js.map +1 -0
- package/dist/controllers/feedback.d.ts +44 -0
- package/dist/controllers/feedback.d.ts.map +1 -0
- package/dist/controllers/feedback.js +216 -0
- package/dist/controllers/feedback.js.map +1 -0
- package/dist/controllers/index.d.ts +9 -0
- package/dist/controllers/index.d.ts.map +1 -0
- package/dist/controllers/index.js +9 -0
- package/dist/controllers/index.js.map +1 -0
- package/dist/controllers/skills.d.ts +66 -0
- package/dist/controllers/skills.d.ts.map +1 -0
- package/dist/controllers/skills.js +355 -0
- package/dist/controllers/skills.js.map +1 -0
- package/dist/controllers/versions.d.ts +43 -0
- package/dist/controllers/versions.d.ts.map +1 -0
- package/dist/controllers/versions.js +298 -0
- package/dist/controllers/versions.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/auth.d.ts +34 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +148 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/error.d.ts +26 -0
- package/dist/middleware/error.d.ts.map +1 -0
- package/dist/middleware/error.js +102 -0
- package/dist/middleware/error.js.map +1 -0
- package/dist/middleware/index.d.ts +8 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +8 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/logger.d.ts +19 -0
- package/dist/middleware/logger.d.ts.map +1 -0
- package/dist/middleware/logger.js +54 -0
- package/dist/middleware/logger.js.map +1 -0
- package/dist/middleware/validation.d.ts +671 -0
- package/dist/middleware/validation.d.ts.map +1 -0
- package/dist/middleware/validation.js +225 -0
- package/dist/middleware/validation.js.map +1 -0
- package/dist/routes/audit.d.ts +6 -0
- package/dist/routes/audit.d.ts.map +1 -0
- package/dist/routes/audit.js +54 -0
- package/dist/routes/audit.js.map +1 -0
- package/dist/routes/cache.d.ts +6 -0
- package/dist/routes/cache.d.ts.map +1 -0
- package/dist/routes/cache.js +70 -0
- package/dist/routes/cache.js.map +1 -0
- package/dist/routes/feedback.d.ts +6 -0
- package/dist/routes/feedback.d.ts.map +1 -0
- package/dist/routes/feedback.js +68 -0
- package/dist/routes/feedback.js.map +1 -0
- package/dist/routes/health.d.ts +6 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +122 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/index.d.ts +12 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +12 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/scan.d.ts +8 -0
- package/dist/routes/scan.d.ts.map +1 -0
- package/dist/routes/scan.js +315 -0
- package/dist/routes/scan.js.map +1 -0
- package/dist/routes/search.d.ts +6 -0
- package/dist/routes/search.d.ts.map +1 -0
- package/dist/routes/search.js +44 -0
- package/dist/routes/search.js.map +1 -0
- package/dist/routes/skills.d.ts +6 -0
- package/dist/routes/skills.d.ts.map +1 -0
- package/dist/routes/skills.js +74 -0
- package/dist/routes/skills.js.map +1 -0
- package/dist/routes/versions.d.ts +6 -0
- package/dist/routes/versions.d.ts.map +1 -0
- package/dist/routes/versions.js +66 -0
- package/dist/routes/versions.js.map +1 -0
- package/dist/server.d.ts +26 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +166 -0
- package/dist/server.js.map +1 -0
- package/package.json +42 -0
- package/src/controllers/audit.ts +175 -0
- package/src/controllers/cache.ts +344 -0
- package/src/controllers/feedback.ts +309 -0
- package/src/controllers/index.ts +9 -0
- package/src/controllers/skills.ts +489 -0
- package/src/controllers/versions.ts +427 -0
- package/src/index.ts +87 -0
- package/src/middleware/auth.ts +219 -0
- package/src/middleware/error.ts +180 -0
- package/src/middleware/index.ts +8 -0
- package/src/middleware/logger.ts +71 -0
- package/src/middleware/validation.ts +270 -0
- package/src/routes/audit.ts +74 -0
- package/src/routes/cache.ts +93 -0
- package/src/routes/feedback.ts +93 -0
- package/src/routes/health.ts +151 -0
- package/src/routes/index.ts +12 -0
- package/src/routes/scan.ts +428 -0
- package/src/routes/search.ts +51 -0
- package/src/routes/skills.ts +102 -0
- package/src/routes/versions.ts +91 -0
- package/src/server.ts +205 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Skills Hub - Versions Controller
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
6
|
+
import {
|
|
7
|
+
getStorage,
|
|
8
|
+
generateUUID,
|
|
9
|
+
now,
|
|
10
|
+
sha256,
|
|
11
|
+
buildSkillFullName,
|
|
12
|
+
parseSkillFullName,
|
|
13
|
+
AppError,
|
|
14
|
+
ErrorCodes,
|
|
15
|
+
} from '@open-skills-hub/core';
|
|
16
|
+
import type { Version, AuditLog } from '@open-skills-hub/core';
|
|
17
|
+
import { sendSuccess } from '../middleware/error.js';
|
|
18
|
+
import { logger } from '../middleware/logger.js';
|
|
19
|
+
import type {
|
|
20
|
+
SkillNameParam,
|
|
21
|
+
SkillVersionParam,
|
|
22
|
+
PublishVersionBody,
|
|
23
|
+
PaginationQuery,
|
|
24
|
+
} from '../middleware/validation.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get all versions of a skill
|
|
28
|
+
*/
|
|
29
|
+
export async function getVersions(
|
|
30
|
+
request: FastifyRequest<{ Params: SkillNameParam; Querystring: PaginationQuery & { includeDeprecated?: string } }>,
|
|
31
|
+
reply: FastifyReply
|
|
32
|
+
): Promise<void> {
|
|
33
|
+
const storage = await getStorage();
|
|
34
|
+
const { name } = request.params;
|
|
35
|
+
const { cursor, limit, includeDeprecated } = request.query;
|
|
36
|
+
|
|
37
|
+
// Parse name
|
|
38
|
+
const { scope, name: skillName } = parseSkillFullName(name);
|
|
39
|
+
const fullName = buildSkillFullName(skillName, scope);
|
|
40
|
+
|
|
41
|
+
const skill = await storage.getSkillByName(fullName);
|
|
42
|
+
if (!skill) {
|
|
43
|
+
throw new AppError(
|
|
44
|
+
ErrorCodes.SKILL_NOT_FOUND,
|
|
45
|
+
`Skill '${fullName}' not found`,
|
|
46
|
+
404
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const result = await storage.getVersions(skill.id, {
|
|
51
|
+
cursor,
|
|
52
|
+
limit,
|
|
53
|
+
includeDeprecated: includeDeprecated === 'true',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Map versions to public format (exclude full content)
|
|
57
|
+
const versions = result.items.map(v => ({
|
|
58
|
+
id: v.id,
|
|
59
|
+
version: v.version,
|
|
60
|
+
tag: v.tag,
|
|
61
|
+
status: v.status,
|
|
62
|
+
securityScore: v.securityScore,
|
|
63
|
+
securityLevel: v.securityLevel,
|
|
64
|
+
uses: v.uses,
|
|
65
|
+
changelog: v.changelog,
|
|
66
|
+
publishedAt: v.publishedAt,
|
|
67
|
+
deprecatedAt: v.deprecatedAt,
|
|
68
|
+
deprecatedMessage: v.deprecatedMessage,
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
sendSuccess(request, reply, versions, 200, result.pagination);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get a specific version
|
|
76
|
+
*/
|
|
77
|
+
export async function getVersion(
|
|
78
|
+
request: FastifyRequest<{ Params: SkillVersionParam }>,
|
|
79
|
+
reply: FastifyReply
|
|
80
|
+
): Promise<void> {
|
|
81
|
+
const storage = await getStorage();
|
|
82
|
+
const { name, version: versionStr } = request.params;
|
|
83
|
+
|
|
84
|
+
// Parse name
|
|
85
|
+
const { scope, name: skillName } = parseSkillFullName(name);
|
|
86
|
+
const fullName = buildSkillFullName(skillName, scope);
|
|
87
|
+
|
|
88
|
+
const skill = await storage.getSkillByName(fullName);
|
|
89
|
+
if (!skill) {
|
|
90
|
+
throw new AppError(
|
|
91
|
+
ErrorCodes.SKILL_NOT_FOUND,
|
|
92
|
+
`Skill '${fullName}' not found`,
|
|
93
|
+
404
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const version = await storage.getVersion(skill.id, versionStr);
|
|
98
|
+
if (!version) {
|
|
99
|
+
throw new AppError(
|
|
100
|
+
ErrorCodes.VERSION_NOT_FOUND,
|
|
101
|
+
`Version '${versionStr}' not found for skill '${fullName}'`,
|
|
102
|
+
404
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
sendSuccess(request, reply, {
|
|
107
|
+
id: version.id,
|
|
108
|
+
skillId: version.skillId,
|
|
109
|
+
version: version.version,
|
|
110
|
+
tag: version.tag,
|
|
111
|
+
status: version.status,
|
|
112
|
+
securityScore: version.securityScore,
|
|
113
|
+
securityLevel: version.securityLevel,
|
|
114
|
+
uses: version.uses,
|
|
115
|
+
changelog: version.changelog,
|
|
116
|
+
packageSize: version.packageSize,
|
|
117
|
+
packageHash: version.packageHash,
|
|
118
|
+
publishedAt: version.publishedAt,
|
|
119
|
+
deprecatedAt: version.deprecatedAt,
|
|
120
|
+
deprecatedMessage: version.deprecatedMessage,
|
|
121
|
+
content: version.content,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Publish a new version
|
|
127
|
+
*/
|
|
128
|
+
export async function publishVersion(
|
|
129
|
+
request: FastifyRequest<{ Params: SkillNameParam; Body: PublishVersionBody }>,
|
|
130
|
+
reply: FastifyReply
|
|
131
|
+
): Promise<void> {
|
|
132
|
+
const storage = await getStorage();
|
|
133
|
+
const { name } = request.params;
|
|
134
|
+
const body = request.body;
|
|
135
|
+
const userId = request.user?.id;
|
|
136
|
+
|
|
137
|
+
// Parse name
|
|
138
|
+
const { scope, name: skillName } = parseSkillFullName(name);
|
|
139
|
+
const fullName = buildSkillFullName(skillName, scope);
|
|
140
|
+
|
|
141
|
+
const skill = await storage.getSkillByName(fullName);
|
|
142
|
+
if (!skill) {
|
|
143
|
+
throw new AppError(
|
|
144
|
+
ErrorCodes.SKILL_NOT_FOUND,
|
|
145
|
+
`Skill '${fullName}' not found`,
|
|
146
|
+
404
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Check ownership
|
|
151
|
+
if (skill.ownerId && skill.ownerId !== userId && request.user?.type !== 'system') {
|
|
152
|
+
throw new AppError(
|
|
153
|
+
ErrorCodes.FORBIDDEN,
|
|
154
|
+
'You do not have permission to publish versions for this skill',
|
|
155
|
+
403
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check if version already exists
|
|
160
|
+
const existingVersion = await storage.getVersion(skill.id, body.version);
|
|
161
|
+
if (existingVersion) {
|
|
162
|
+
throw new AppError(
|
|
163
|
+
ErrorCodes.VERSION_EXISTS,
|
|
164
|
+
`Version '${body.version}' already exists for skill '${fullName}'`,
|
|
165
|
+
409
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Calculate content hash
|
|
170
|
+
const contentString = JSON.stringify(body.content);
|
|
171
|
+
const contentHash = sha256(contentString);
|
|
172
|
+
|
|
173
|
+
const versionId = generateUUID();
|
|
174
|
+
const timestamp = now();
|
|
175
|
+
|
|
176
|
+
// Create version
|
|
177
|
+
const version: Version = {
|
|
178
|
+
id: versionId,
|
|
179
|
+
skillId: skill.id,
|
|
180
|
+
version: body.version,
|
|
181
|
+
tag: body.tag,
|
|
182
|
+
content: body.content,
|
|
183
|
+
packageUrl: `local://${fullName}/${body.version}`,
|
|
184
|
+
packageSize: contentString.length,
|
|
185
|
+
packageHash: contentHash,
|
|
186
|
+
changelog: body.changelog,
|
|
187
|
+
status: 'published',
|
|
188
|
+
publishedBy: userId,
|
|
189
|
+
uses: 0,
|
|
190
|
+
createdAt: timestamp,
|
|
191
|
+
publishedAt: timestamp,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
await storage.createVersion(version);
|
|
195
|
+
|
|
196
|
+
// Update 'latest' tag on previous version
|
|
197
|
+
const versions = await storage.getVersions(skill.id, { limit: 100 });
|
|
198
|
+
for (const v of versions.items) {
|
|
199
|
+
if (v.id !== versionId && v.tag === 'latest') {
|
|
200
|
+
await storage.updateVersion(v.id, { tag: undefined });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Set latest tag on new version if specified
|
|
205
|
+
if (body.tag === 'latest' || !body.tag) {
|
|
206
|
+
await storage.updateVersion(versionId, { tag: 'latest' });
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Update skill's latest version
|
|
210
|
+
await storage.updateSkill(skill.id, {
|
|
211
|
+
latestVersion: body.version,
|
|
212
|
+
latestVersionId: versionId,
|
|
213
|
+
stats: {
|
|
214
|
+
...skill.stats,
|
|
215
|
+
versionCount: skill.stats.versionCount + 1,
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Create audit log
|
|
220
|
+
const auditLog: AuditLog = {
|
|
221
|
+
id: generateUUID(),
|
|
222
|
+
timestamp,
|
|
223
|
+
eventType: 'version.published',
|
|
224
|
+
actor: {
|
|
225
|
+
type: request.user?.type ?? 'user',
|
|
226
|
+
id: userId,
|
|
227
|
+
username: request.user?.username,
|
|
228
|
+
ip: request.ip,
|
|
229
|
+
userAgent: request.headers['user-agent'],
|
|
230
|
+
},
|
|
231
|
+
resource: {
|
|
232
|
+
type: 'version',
|
|
233
|
+
id: versionId,
|
|
234
|
+
name: fullName,
|
|
235
|
+
version: body.version,
|
|
236
|
+
},
|
|
237
|
+
action: 'create',
|
|
238
|
+
result: 'success',
|
|
239
|
+
details: {
|
|
240
|
+
contentHash,
|
|
241
|
+
packageSize: contentString.length,
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
await storage.createAuditLog(auditLog);
|
|
245
|
+
|
|
246
|
+
logger.info('Version published', {
|
|
247
|
+
skillId: skill.id,
|
|
248
|
+
fullName,
|
|
249
|
+
version: body.version,
|
|
250
|
+
versionId,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
sendSuccess(request, reply, {
|
|
254
|
+
id: version.id,
|
|
255
|
+
version: version.version,
|
|
256
|
+
tag: body.tag ?? 'latest',
|
|
257
|
+
publishedAt: version.publishedAt,
|
|
258
|
+
packageHash: version.packageHash,
|
|
259
|
+
}, 201);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Deprecate a version
|
|
264
|
+
*/
|
|
265
|
+
export async function deprecateVersion(
|
|
266
|
+
request: FastifyRequest<{ Params: SkillVersionParam; Body: { message?: string } }>,
|
|
267
|
+
reply: FastifyReply
|
|
268
|
+
): Promise<void> {
|
|
269
|
+
const storage = await getStorage();
|
|
270
|
+
const { name, version: versionStr } = request.params;
|
|
271
|
+
const { message } = request.body;
|
|
272
|
+
const userId = request.user?.id;
|
|
273
|
+
|
|
274
|
+
// Parse name
|
|
275
|
+
const { scope, name: skillName } = parseSkillFullName(name);
|
|
276
|
+
const fullName = buildSkillFullName(skillName, scope);
|
|
277
|
+
|
|
278
|
+
const skill = await storage.getSkillByName(fullName);
|
|
279
|
+
if (!skill) {
|
|
280
|
+
throw new AppError(
|
|
281
|
+
ErrorCodes.SKILL_NOT_FOUND,
|
|
282
|
+
`Skill '${fullName}' not found`,
|
|
283
|
+
404
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Check ownership
|
|
288
|
+
if (skill.ownerId && skill.ownerId !== userId && request.user?.type !== 'system') {
|
|
289
|
+
throw new AppError(
|
|
290
|
+
ErrorCodes.FORBIDDEN,
|
|
291
|
+
'You do not have permission to deprecate versions for this skill',
|
|
292
|
+
403
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const version = await storage.getVersion(skill.id, versionStr);
|
|
297
|
+
if (!version) {
|
|
298
|
+
throw new AppError(
|
|
299
|
+
ErrorCodes.VERSION_NOT_FOUND,
|
|
300
|
+
`Version '${versionStr}' not found for skill '${fullName}'`,
|
|
301
|
+
404
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const deprecatedVersion = await storage.deprecateVersion(
|
|
306
|
+
version.id,
|
|
307
|
+
message ?? 'This version has been deprecated'
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
// Create audit log
|
|
311
|
+
const auditLog: AuditLog = {
|
|
312
|
+
id: generateUUID(),
|
|
313
|
+
timestamp: now(),
|
|
314
|
+
eventType: 'version.deprecated',
|
|
315
|
+
actor: {
|
|
316
|
+
type: request.user?.type ?? 'user',
|
|
317
|
+
id: userId,
|
|
318
|
+
username: request.user?.username,
|
|
319
|
+
ip: request.ip,
|
|
320
|
+
userAgent: request.headers['user-agent'],
|
|
321
|
+
},
|
|
322
|
+
resource: {
|
|
323
|
+
type: 'version',
|
|
324
|
+
id: version.id,
|
|
325
|
+
name: fullName,
|
|
326
|
+
version: versionStr,
|
|
327
|
+
},
|
|
328
|
+
action: 'deprecate',
|
|
329
|
+
result: 'success',
|
|
330
|
+
details: { message },
|
|
331
|
+
};
|
|
332
|
+
await storage.createAuditLog(auditLog);
|
|
333
|
+
|
|
334
|
+
logger.info('Version deprecated', {
|
|
335
|
+
skillId: skill.id,
|
|
336
|
+
fullName,
|
|
337
|
+
version: versionStr,
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
sendSuccess(request, reply, {
|
|
341
|
+
id: deprecatedVersion?.id,
|
|
342
|
+
version: deprecatedVersion?.version,
|
|
343
|
+
status: deprecatedVersion?.status,
|
|
344
|
+
deprecatedAt: deprecatedVersion?.deprecatedAt,
|
|
345
|
+
deprecatedMessage: deprecatedVersion?.deprecatedMessage,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Delete a version (yank)
|
|
351
|
+
*/
|
|
352
|
+
export async function deleteVersion(
|
|
353
|
+
request: FastifyRequest<{ Params: SkillVersionParam }>,
|
|
354
|
+
reply: FastifyReply
|
|
355
|
+
): Promise<void> {
|
|
356
|
+
const storage = await getStorage();
|
|
357
|
+
const { name, version: versionStr } = request.params;
|
|
358
|
+
const userId = request.user?.id;
|
|
359
|
+
|
|
360
|
+
// Parse name
|
|
361
|
+
const { scope, name: skillName } = parseSkillFullName(name);
|
|
362
|
+
const fullName = buildSkillFullName(skillName, scope);
|
|
363
|
+
|
|
364
|
+
const skill = await storage.getSkillByName(fullName);
|
|
365
|
+
if (!skill) {
|
|
366
|
+
throw new AppError(
|
|
367
|
+
ErrorCodes.SKILL_NOT_FOUND,
|
|
368
|
+
`Skill '${fullName}' not found`,
|
|
369
|
+
404
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Check ownership
|
|
374
|
+
if (skill.ownerId && skill.ownerId !== userId && request.user?.type !== 'system') {
|
|
375
|
+
throw new AppError(
|
|
376
|
+
ErrorCodes.FORBIDDEN,
|
|
377
|
+
'You do not have permission to delete versions for this skill',
|
|
378
|
+
403
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const version = await storage.getVersion(skill.id, versionStr);
|
|
383
|
+
if (!version) {
|
|
384
|
+
throw new AppError(
|
|
385
|
+
ErrorCodes.VERSION_NOT_FOUND,
|
|
386
|
+
`Version '${versionStr}' not found for skill '${fullName}'`,
|
|
387
|
+
404
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Instead of hard delete, mark as yanked
|
|
392
|
+
await storage.updateVersion(version.id, {
|
|
393
|
+
status: 'yanked',
|
|
394
|
+
tag: undefined, // Remove any tag
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// If this was the latest version, update skill's latestVersion
|
|
398
|
+
if (skill.latestVersion === versionStr) {
|
|
399
|
+
// Find next latest version
|
|
400
|
+
const versions = await storage.getVersions(skill.id, { limit: 10 });
|
|
401
|
+
const nextLatest = versions.items.find(v => v.id !== version.id && v.status === 'published');
|
|
402
|
+
|
|
403
|
+
if (nextLatest) {
|
|
404
|
+
await storage.updateSkill(skill.id, {
|
|
405
|
+
latestVersion: nextLatest.version,
|
|
406
|
+
latestVersionId: nextLatest.id,
|
|
407
|
+
});
|
|
408
|
+
await storage.updateVersion(nextLatest.id, { tag: 'latest' });
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Update version count
|
|
413
|
+
await storage.updateSkill(skill.id, {
|
|
414
|
+
stats: {
|
|
415
|
+
...skill.stats,
|
|
416
|
+
versionCount: Math.max(0, skill.stats.versionCount - 1),
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
logger.info('Version yanked', {
|
|
421
|
+
skillId: skill.id,
|
|
422
|
+
fullName,
|
|
423
|
+
version: versionStr,
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
sendSuccess(request, reply, { yanked: true });
|
|
427
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Open Skills Hub - API Entry Point
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createServer, startServer, stopServer } from './server.js';
|
|
7
|
+
import { initConfig, logger } from '@open-skills-hub/core';
|
|
8
|
+
|
|
9
|
+
// Export server utilities
|
|
10
|
+
export { createServer, startServer, stopServer } from './server.js';
|
|
11
|
+
export * from './middleware/index.js';
|
|
12
|
+
export * from './controllers/index.js';
|
|
13
|
+
export * from './routes/index.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Main entry point
|
|
17
|
+
*/
|
|
18
|
+
async function main(): Promise<void> {
|
|
19
|
+
// Initialize configuration
|
|
20
|
+
const config = initConfig();
|
|
21
|
+
await config.load();
|
|
22
|
+
|
|
23
|
+
logger.info('Starting Open Skills Hub API', {
|
|
24
|
+
mode: config.get().mode,
|
|
25
|
+
port: config.get().server.port,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Create and start server
|
|
29
|
+
const server = await createServer();
|
|
30
|
+
|
|
31
|
+
// Handle shutdown signals
|
|
32
|
+
const shutdown = async (signal: string) => {
|
|
33
|
+
logger.info(`Received ${signal}, shutting down...`);
|
|
34
|
+
try {
|
|
35
|
+
await stopServer(server);
|
|
36
|
+
process.exit(0);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
logger.error('Error during shutdown', { error });
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
44
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
45
|
+
|
|
46
|
+
// Handle uncaught errors
|
|
47
|
+
process.on('uncaughtException', (error) => {
|
|
48
|
+
logger.error('Uncaught exception', { error });
|
|
49
|
+
process.exit(1);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
process.on('unhandledRejection', (reason) => {
|
|
53
|
+
logger.error('Unhandled rejection', { reason });
|
|
54
|
+
process.exit(1);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Start the server
|
|
58
|
+
try {
|
|
59
|
+
const address = await startServer(server);
|
|
60
|
+
logger.info(`Open Skills Hub API started at ${address}`);
|
|
61
|
+
|
|
62
|
+
// Log available endpoints
|
|
63
|
+
logger.info('Available endpoints:', {
|
|
64
|
+
health: `${address}/health`,
|
|
65
|
+
ready: `${address}/ready`,
|
|
66
|
+
skills: `${address}/v1/skills`,
|
|
67
|
+
search: `${address}/v1/search`,
|
|
68
|
+
scan: `${address}/v1/scan`,
|
|
69
|
+
feedback: `${address}/v1/feedback`,
|
|
70
|
+
cache: `${address}/v1/cache`,
|
|
71
|
+
audit: `${address}/v1/audit`,
|
|
72
|
+
});
|
|
73
|
+
} catch (error) {
|
|
74
|
+
logger.error('Failed to start server', { error });
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Run if executed directly
|
|
80
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}` ||
|
|
81
|
+
decodeURIComponent(import.meta.url) === `file://${process.argv[1]}`;
|
|
82
|
+
if (isMainModule) {
|
|
83
|
+
main().catch((error) => {
|
|
84
|
+
console.error('Fatal error:', error);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
});
|
|
87
|
+
}
|