@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,428 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Skills Hub - Scan Routes
|
|
3
|
+
*
|
|
4
|
+
* Security scanning endpoints using SkillsGuard
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { FastifyInstance, FastifyPluginOptions, FastifyRequest, FastifyReply } from 'fastify';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import {
|
|
10
|
+
getStorage,
|
|
11
|
+
getConfig,
|
|
12
|
+
initScanner,
|
|
13
|
+
getScanner,
|
|
14
|
+
generateUUID,
|
|
15
|
+
now,
|
|
16
|
+
buildSkillFullName,
|
|
17
|
+
parseSkillFullName,
|
|
18
|
+
AppError,
|
|
19
|
+
ErrorCodes,
|
|
20
|
+
} from '@open-skills-hub/core';
|
|
21
|
+
import type { AuditLog } from '@open-skills-hub/core';
|
|
22
|
+
import { sendSuccess } from '../middleware/error.js';
|
|
23
|
+
import { logger } from '../middleware/logger.js';
|
|
24
|
+
import { optionalAuth, requireAuth } from '../middleware/auth.js';
|
|
25
|
+
import {
|
|
26
|
+
validateBody,
|
|
27
|
+
validateParams,
|
|
28
|
+
validateQuery,
|
|
29
|
+
SkillNameParamSchema,
|
|
30
|
+
ScanContentBodySchema,
|
|
31
|
+
PaginationQuerySchema,
|
|
32
|
+
IdParamSchema,
|
|
33
|
+
} from '../middleware/validation.js';
|
|
34
|
+
|
|
35
|
+
// Initialize scanner on first use
|
|
36
|
+
let scannerInitialized = false;
|
|
37
|
+
|
|
38
|
+
function ensureScannerInitialized() {
|
|
39
|
+
if (!scannerInitialized) {
|
|
40
|
+
const config = getConfig().get();
|
|
41
|
+
initScanner({
|
|
42
|
+
enabled: config.scanner.enabled,
|
|
43
|
+
timeout: config.scanner.timeout,
|
|
44
|
+
maxFileSize: config.scanner.maxFileSize,
|
|
45
|
+
rulesPath: config.scanner.rulesPath,
|
|
46
|
+
});
|
|
47
|
+
scannerInitialized = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Scan content
|
|
53
|
+
*/
|
|
54
|
+
async function scanContent(
|
|
55
|
+
request: FastifyRequest<{ Body: z.infer<typeof ScanContentBodySchema> }>,
|
|
56
|
+
reply: FastifyReply
|
|
57
|
+
): Promise<void> {
|
|
58
|
+
ensureScannerInitialized();
|
|
59
|
+
|
|
60
|
+
const storage = await getStorage();
|
|
61
|
+
const scanner = getScanner();
|
|
62
|
+
const { content } = request.body;
|
|
63
|
+
const userId = request.user?.id;
|
|
64
|
+
|
|
65
|
+
// Run the scan
|
|
66
|
+
const result = await scanner.scan(content);
|
|
67
|
+
|
|
68
|
+
// Save scan record and issues to storage
|
|
69
|
+
await storage.createScanRecord({
|
|
70
|
+
...result.record,
|
|
71
|
+
userId,
|
|
72
|
+
inputType: 'api',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (result.issues.length > 0) {
|
|
76
|
+
await storage.createScanIssues(result.issues);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Create audit log
|
|
80
|
+
const auditLog: AuditLog = {
|
|
81
|
+
id: generateUUID(),
|
|
82
|
+
timestamp: now(),
|
|
83
|
+
eventType: 'scan.completed',
|
|
84
|
+
actor: {
|
|
85
|
+
type: request.user?.type ?? 'user',
|
|
86
|
+
id: userId,
|
|
87
|
+
username: request.user?.username,
|
|
88
|
+
ip: request.ip,
|
|
89
|
+
userAgent: request.headers['user-agent'],
|
|
90
|
+
},
|
|
91
|
+
resource: {
|
|
92
|
+
type: 'skill',
|
|
93
|
+
id: result.record.id,
|
|
94
|
+
},
|
|
95
|
+
action: 'scan',
|
|
96
|
+
result: 'success',
|
|
97
|
+
details: {
|
|
98
|
+
score: result.summary.score,
|
|
99
|
+
level: result.summary.level,
|
|
100
|
+
issueCount: result.summary.issueCount,
|
|
101
|
+
duration: result.record.duration,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
await storage.createAuditLog(auditLog);
|
|
105
|
+
|
|
106
|
+
// Log high-risk scans
|
|
107
|
+
if (result.summary.level === 'high') {
|
|
108
|
+
const highRiskLog: AuditLog = {
|
|
109
|
+
id: generateUUID(),
|
|
110
|
+
timestamp: now(),
|
|
111
|
+
eventType: 'scan.high_risk',
|
|
112
|
+
actor: {
|
|
113
|
+
type: request.user?.type ?? 'user',
|
|
114
|
+
id: userId,
|
|
115
|
+
username: request.user?.username,
|
|
116
|
+
ip: request.ip,
|
|
117
|
+
},
|
|
118
|
+
resource: {
|
|
119
|
+
type: 'skill',
|
|
120
|
+
id: result.record.id,
|
|
121
|
+
},
|
|
122
|
+
action: 'scan',
|
|
123
|
+
result: 'success',
|
|
124
|
+
details: {
|
|
125
|
+
score: result.summary.score,
|
|
126
|
+
highSeverityIssues: result.summary.issueCount.high,
|
|
127
|
+
riskCategories: result.summary.riskCategories,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
await storage.createAuditLog(highRiskLog);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
logger.info('Scan completed', {
|
|
134
|
+
recordId: result.record.id,
|
|
135
|
+
score: result.summary.score,
|
|
136
|
+
level: result.summary.level,
|
|
137
|
+
issueCount: result.summary.issueCount,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
sendSuccess(request, reply, {
|
|
141
|
+
id: result.record.id,
|
|
142
|
+
score: result.summary.score,
|
|
143
|
+
level: result.summary.level,
|
|
144
|
+
issueCount: result.summary.issueCount,
|
|
145
|
+
issues: result.issues.map(i => ({
|
|
146
|
+
id: i.id,
|
|
147
|
+
ruleId: i.ruleId,
|
|
148
|
+
ruleName: i.ruleName,
|
|
149
|
+
category: i.ruleCategory,
|
|
150
|
+
severity: i.severity,
|
|
151
|
+
line: i.line,
|
|
152
|
+
column: i.column,
|
|
153
|
+
content: i.content,
|
|
154
|
+
message: i.message,
|
|
155
|
+
suggestion: i.suggestion,
|
|
156
|
+
})),
|
|
157
|
+
recommendations: result.summary.recommendations,
|
|
158
|
+
duration: result.record.duration,
|
|
159
|
+
scannerVersion: result.record.scannerVersion,
|
|
160
|
+
rulesApplied: result.record.rulesApplied,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Quick check content (high-severity only)
|
|
166
|
+
*/
|
|
167
|
+
async function quickCheck(
|
|
168
|
+
request: FastifyRequest<{ Body: z.infer<typeof ScanContentBodySchema> }>,
|
|
169
|
+
reply: FastifyReply
|
|
170
|
+
): Promise<void> {
|
|
171
|
+
ensureScannerInitialized();
|
|
172
|
+
|
|
173
|
+
const scanner = getScanner();
|
|
174
|
+
const { content } = request.body;
|
|
175
|
+
|
|
176
|
+
const result = await scanner.quickCheck(content);
|
|
177
|
+
|
|
178
|
+
sendSuccess(request, reply, result);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get scan records for a skill
|
|
183
|
+
*/
|
|
184
|
+
async function getSkillScans(
|
|
185
|
+
request: FastifyRequest<{ Params: z.infer<typeof SkillNameParamSchema>; Querystring: z.infer<typeof PaginationQuerySchema> }>,
|
|
186
|
+
reply: FastifyReply
|
|
187
|
+
): Promise<void> {
|
|
188
|
+
const storage = await getStorage();
|
|
189
|
+
const { name } = request.params;
|
|
190
|
+
const { cursor, limit } = request.query;
|
|
191
|
+
|
|
192
|
+
// Parse name
|
|
193
|
+
const { scope, name: skillName } = parseSkillFullName(name);
|
|
194
|
+
const fullName = buildSkillFullName(skillName, scope);
|
|
195
|
+
|
|
196
|
+
const skill = await storage.getSkillByName(fullName);
|
|
197
|
+
if (!skill) {
|
|
198
|
+
throw new AppError(
|
|
199
|
+
ErrorCodes.SKILL_NOT_FOUND,
|
|
200
|
+
`Skill '${fullName}' not found`,
|
|
201
|
+
404
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const result = await storage.getScanRecords(skill.id, { cursor, limit });
|
|
206
|
+
|
|
207
|
+
sendSuccess(request, reply, result.items, 200, result.pagination);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get scan record by ID
|
|
212
|
+
*/
|
|
213
|
+
async function getScanRecord(
|
|
214
|
+
request: FastifyRequest<{ Params: z.infer<typeof IdParamSchema> }>,
|
|
215
|
+
reply: FastifyReply
|
|
216
|
+
): Promise<void> {
|
|
217
|
+
const storage = await getStorage();
|
|
218
|
+
const { id } = request.params;
|
|
219
|
+
|
|
220
|
+
const record = await storage.getScanRecordById(id);
|
|
221
|
+
if (!record) {
|
|
222
|
+
throw new AppError(
|
|
223
|
+
ErrorCodes.SKILL_NOT_FOUND,
|
|
224
|
+
`Scan record '${id}' not found`,
|
|
225
|
+
404
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Get issues if scan is completed
|
|
230
|
+
let issues: unknown[] = [];
|
|
231
|
+
if (record.status === 'completed') {
|
|
232
|
+
issues = await storage.getScanIssues(id);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
sendSuccess(request, reply, {
|
|
236
|
+
...record,
|
|
237
|
+
issues,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Get scan issues for a record
|
|
243
|
+
*/
|
|
244
|
+
async function getScanIssues(
|
|
245
|
+
request: FastifyRequest<{ Params: z.infer<typeof IdParamSchema> }>,
|
|
246
|
+
reply: FastifyReply
|
|
247
|
+
): Promise<void> {
|
|
248
|
+
const storage = await getStorage();
|
|
249
|
+
const { id } = request.params;
|
|
250
|
+
|
|
251
|
+
const record = await storage.getScanRecordById(id);
|
|
252
|
+
if (!record) {
|
|
253
|
+
throw new AppError(
|
|
254
|
+
ErrorCodes.SKILL_NOT_FOUND,
|
|
255
|
+
`Scan record '${id}' not found`,
|
|
256
|
+
404
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const issues = await storage.getScanIssues(id);
|
|
261
|
+
|
|
262
|
+
sendSuccess(request, reply, issues);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Acknowledge an issue
|
|
267
|
+
*/
|
|
268
|
+
async function acknowledgeIssue(
|
|
269
|
+
request: FastifyRequest<{ Params: z.infer<typeof IdParamSchema>; Body: { reason?: string } }>,
|
|
270
|
+
reply: FastifyReply
|
|
271
|
+
): Promise<void> {
|
|
272
|
+
const storage = await getStorage();
|
|
273
|
+
const { id } = request.params;
|
|
274
|
+
const { reason } = request.body;
|
|
275
|
+
const userId = request.user?.id ?? 'unknown';
|
|
276
|
+
|
|
277
|
+
const issue = await storage.acknowledgeIssue(id, userId, reason);
|
|
278
|
+
if (!issue) {
|
|
279
|
+
throw new AppError(
|
|
280
|
+
ErrorCodes.SKILL_NOT_FOUND,
|
|
281
|
+
`Issue '${id}' not found`,
|
|
282
|
+
404
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
logger.info('Issue acknowledged', { issueId: id, userId, reason });
|
|
287
|
+
|
|
288
|
+
sendSuccess(request, reply, issue);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Get scanner rules
|
|
293
|
+
*/
|
|
294
|
+
async function getScannerRules(
|
|
295
|
+
request: FastifyRequest,
|
|
296
|
+
reply: FastifyReply
|
|
297
|
+
): Promise<void> {
|
|
298
|
+
ensureScannerInitialized();
|
|
299
|
+
|
|
300
|
+
const scanner = getScanner();
|
|
301
|
+
const rules = scanner.getEnabledRules();
|
|
302
|
+
|
|
303
|
+
sendSuccess(request, reply, {
|
|
304
|
+
version: scanner.getVersion(),
|
|
305
|
+
rulesCount: rules.length,
|
|
306
|
+
rules: rules.map(r => ({
|
|
307
|
+
id: r.id,
|
|
308
|
+
name: r.name,
|
|
309
|
+
description: r.description,
|
|
310
|
+
category: r.category,
|
|
311
|
+
severity: r.severity,
|
|
312
|
+
enabled: r.enabled,
|
|
313
|
+
isBuiltin: r.isBuiltin,
|
|
314
|
+
})),
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Get scanner statistics
|
|
320
|
+
*/
|
|
321
|
+
async function getScannerStats(
|
|
322
|
+
request: FastifyRequest,
|
|
323
|
+
reply: FastifyReply
|
|
324
|
+
): Promise<void> {
|
|
325
|
+
ensureScannerInitialized();
|
|
326
|
+
|
|
327
|
+
const scanner = getScanner();
|
|
328
|
+
const rules = scanner.getEnabledRules();
|
|
329
|
+
|
|
330
|
+
// Count rules by category and severity
|
|
331
|
+
const byCategory: Record<string, number> = {};
|
|
332
|
+
const bySeverity: Record<string, number> = {};
|
|
333
|
+
|
|
334
|
+
for (const rule of rules) {
|
|
335
|
+
byCategory[rule.category] = (byCategory[rule.category] || 0) + 1;
|
|
336
|
+
bySeverity[rule.severity] = (bySeverity[rule.severity] || 0) + 1;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
sendSuccess(request, reply, {
|
|
340
|
+
version: scanner.getVersion(),
|
|
341
|
+
totalRules: rules.length,
|
|
342
|
+
enabledRules: rules.filter(r => r.enabled).length,
|
|
343
|
+
builtinRules: rules.filter(r => r.isBuiltin).length,
|
|
344
|
+
customRules: rules.filter(r => !r.isBuiltin).length,
|
|
345
|
+
byCategory,
|
|
346
|
+
bySeverity,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export async function scanRoutes(
|
|
351
|
+
server: FastifyInstance,
|
|
352
|
+
_opts: FastifyPluginOptions
|
|
353
|
+
): Promise<void> {
|
|
354
|
+
// Scan content
|
|
355
|
+
// POST /v1/scan
|
|
356
|
+
server.post('/', {
|
|
357
|
+
preHandler: [
|
|
358
|
+
optionalAuth,
|
|
359
|
+
validateBody(ScanContentBodySchema),
|
|
360
|
+
],
|
|
361
|
+
handler: scanContent,
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// Quick check (high-severity only)
|
|
365
|
+
// POST /v1/scan/quick
|
|
366
|
+
server.post('/quick', {
|
|
367
|
+
preHandler: [
|
|
368
|
+
optionalAuth,
|
|
369
|
+
validateBody(ScanContentBodySchema),
|
|
370
|
+
],
|
|
371
|
+
handler: quickCheck,
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// Get scanner rules
|
|
375
|
+
// GET /v1/scan/rules
|
|
376
|
+
server.get('/rules', {
|
|
377
|
+
preHandler: [optionalAuth],
|
|
378
|
+
handler: getScannerRules,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// Get scanner statistics
|
|
382
|
+
// GET /v1/scan/stats
|
|
383
|
+
server.get('/stats', {
|
|
384
|
+
preHandler: [optionalAuth],
|
|
385
|
+
handler: getScannerStats,
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// Get scan records for a skill
|
|
389
|
+
// GET /v1/scan/skill/:name
|
|
390
|
+
server.get('/skill/:name', {
|
|
391
|
+
preHandler: [
|
|
392
|
+
optionalAuth,
|
|
393
|
+
validateParams(SkillNameParamSchema),
|
|
394
|
+
validateQuery(PaginationQuerySchema),
|
|
395
|
+
],
|
|
396
|
+
handler: getSkillScans,
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// Get scan record by ID
|
|
400
|
+
// GET /v1/scan/:id
|
|
401
|
+
server.get('/:id', {
|
|
402
|
+
preHandler: [
|
|
403
|
+
optionalAuth,
|
|
404
|
+
validateParams(IdParamSchema),
|
|
405
|
+
],
|
|
406
|
+
handler: getScanRecord,
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// Get issues for a scan record
|
|
410
|
+
// GET /v1/scan/:id/issues
|
|
411
|
+
server.get('/:id/issues', {
|
|
412
|
+
preHandler: [
|
|
413
|
+
optionalAuth,
|
|
414
|
+
validateParams(IdParamSchema),
|
|
415
|
+
],
|
|
416
|
+
handler: getScanIssues,
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// Acknowledge an issue
|
|
420
|
+
// POST /v1/scan/issues/:id/acknowledge
|
|
421
|
+
server.post('/issues/:id/acknowledge', {
|
|
422
|
+
preHandler: [
|
|
423
|
+
requireAuth,
|
|
424
|
+
validateParams(IdParamSchema),
|
|
425
|
+
],
|
|
426
|
+
handler: acknowledgeIssue,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Skills Hub - Search Routes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { FastifyInstance, FastifyPluginOptions, FastifyRequest, FastifyReply } from 'fastify';
|
|
6
|
+
import { searchSkills, type SearchQuery } from '../controllers/skills.js';
|
|
7
|
+
import { optionalAuth } from '../middleware/auth.js';
|
|
8
|
+
import { validateQuery, SearchQuerySchema } from '../middleware/validation.js';
|
|
9
|
+
|
|
10
|
+
export async function searchRoutes(
|
|
11
|
+
server: FastifyInstance,
|
|
12
|
+
_opts: FastifyPluginOptions
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
// Full-text search
|
|
15
|
+
// GET /v1/search?q=...&category=...&sort=...
|
|
16
|
+
server.get('/', {
|
|
17
|
+
preHandler: [
|
|
18
|
+
optionalAuth,
|
|
19
|
+
validateQuery(SearchQuerySchema),
|
|
20
|
+
],
|
|
21
|
+
handler: searchSkills as (request: FastifyRequest, reply: FastifyReply) => Promise<void>,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Search by category (convenience endpoint)
|
|
25
|
+
// GET /v1/search/category/:category
|
|
26
|
+
server.get('/category/:category', {
|
|
27
|
+
preHandler: [
|
|
28
|
+
optionalAuth,
|
|
29
|
+
],
|
|
30
|
+
handler: async (request, reply) => {
|
|
31
|
+
const { category } = request.params as { category: string };
|
|
32
|
+
// Forward to main search with category filter
|
|
33
|
+
(request.query as SearchQuery).category = category;
|
|
34
|
+
return (searchSkills as (request: FastifyRequest, reply: FastifyReply) => Promise<void>)(request, reply);
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Search by author (convenience endpoint)
|
|
39
|
+
// GET /v1/search/author/:author
|
|
40
|
+
server.get('/author/:author', {
|
|
41
|
+
preHandler: [
|
|
42
|
+
optionalAuth,
|
|
43
|
+
],
|
|
44
|
+
handler: async (request, reply) => {
|
|
45
|
+
const { author } = request.params as { author: string };
|
|
46
|
+
// Forward to main search with author filter
|
|
47
|
+
(request.query as SearchQuery).author = author;
|
|
48
|
+
return (searchSkills as (request: FastifyRequest, reply: FastifyReply) => Promise<void>)(request, reply);
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Skills Hub - Skills Routes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
|
6
|
+
import {
|
|
7
|
+
searchSkills,
|
|
8
|
+
getSkillByName,
|
|
9
|
+
getSkillContent,
|
|
10
|
+
createSkill,
|
|
11
|
+
updateSkill,
|
|
12
|
+
deleteSkill,
|
|
13
|
+
getDerivedSkills,
|
|
14
|
+
} from '../controllers/skills.js';
|
|
15
|
+
import { optionalAuth, requireAuth } from '../middleware/auth.js';
|
|
16
|
+
import {
|
|
17
|
+
validateBody,
|
|
18
|
+
validateParams,
|
|
19
|
+
validateQuery,
|
|
20
|
+
SkillNameParamSchema,
|
|
21
|
+
SearchQuerySchema,
|
|
22
|
+
CreateSkillBodySchema,
|
|
23
|
+
UpdateSkillBodySchema,
|
|
24
|
+
PaginationQuerySchema,
|
|
25
|
+
} from '../middleware/validation.js';
|
|
26
|
+
|
|
27
|
+
export async function skillsRoutes(
|
|
28
|
+
server: FastifyInstance,
|
|
29
|
+
_opts: FastifyPluginOptions
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
// Search skills
|
|
32
|
+
// GET /v1/skills?q=...&category=...&author=...
|
|
33
|
+
server.get('/', {
|
|
34
|
+
preHandler: [
|
|
35
|
+
optionalAuth,
|
|
36
|
+
validateQuery(SearchQuerySchema),
|
|
37
|
+
],
|
|
38
|
+
handler: searchSkills,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Get skill by name
|
|
42
|
+
// GET /v1/skills/:name
|
|
43
|
+
server.get('/:name', {
|
|
44
|
+
preHandler: [
|
|
45
|
+
optionalAuth,
|
|
46
|
+
validateParams(SkillNameParamSchema),
|
|
47
|
+
],
|
|
48
|
+
handler: getSkillByName,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Get skill content (fetch-on-use)
|
|
52
|
+
// GET /v1/skills/:name/content?version=...
|
|
53
|
+
server.get('/:name/content', {
|
|
54
|
+
preHandler: [
|
|
55
|
+
optionalAuth,
|
|
56
|
+
validateParams(SkillNameParamSchema),
|
|
57
|
+
],
|
|
58
|
+
handler: getSkillContent,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Create/publish a new skill
|
|
62
|
+
// POST /v1/skills
|
|
63
|
+
server.post('/', {
|
|
64
|
+
preHandler: [
|
|
65
|
+
requireAuth,
|
|
66
|
+
validateBody(CreateSkillBodySchema),
|
|
67
|
+
],
|
|
68
|
+
handler: createSkill,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Update skill metadata
|
|
72
|
+
// PATCH /v1/skills/:name
|
|
73
|
+
server.patch('/:name', {
|
|
74
|
+
preHandler: [
|
|
75
|
+
requireAuth,
|
|
76
|
+
validateParams(SkillNameParamSchema),
|
|
77
|
+
validateBody(UpdateSkillBodySchema),
|
|
78
|
+
],
|
|
79
|
+
handler: updateSkill,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Delete skill
|
|
83
|
+
// DELETE /v1/skills/:name?hard=true
|
|
84
|
+
server.delete('/:name', {
|
|
85
|
+
preHandler: [
|
|
86
|
+
requireAuth,
|
|
87
|
+
validateParams(SkillNameParamSchema),
|
|
88
|
+
],
|
|
89
|
+
handler: deleteSkill,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Get derived skills
|
|
93
|
+
// GET /v1/skills/:name/derived
|
|
94
|
+
server.get('/:name/derived', {
|
|
95
|
+
preHandler: [
|
|
96
|
+
optionalAuth,
|
|
97
|
+
validateParams(SkillNameParamSchema),
|
|
98
|
+
validateQuery(PaginationQuerySchema),
|
|
99
|
+
],
|
|
100
|
+
handler: getDerivedSkills,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Skills Hub - Versions Routes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import {
|
|
8
|
+
getVersions,
|
|
9
|
+
getVersion,
|
|
10
|
+
publishVersion,
|
|
11
|
+
deprecateVersion,
|
|
12
|
+
deleteVersion,
|
|
13
|
+
} from '../controllers/versions.js';
|
|
14
|
+
import { optionalAuth, requireAuth } from '../middleware/auth.js';
|
|
15
|
+
import {
|
|
16
|
+
validateBody,
|
|
17
|
+
validateParams,
|
|
18
|
+
validateQuery,
|
|
19
|
+
SkillNameParamSchema,
|
|
20
|
+
SkillVersionParamSchema,
|
|
21
|
+
PublishVersionBodySchema,
|
|
22
|
+
PaginationQuerySchema,
|
|
23
|
+
} from '../middleware/validation.js';
|
|
24
|
+
|
|
25
|
+
// Extended query schema for versions
|
|
26
|
+
const VersionsQuerySchema = PaginationQuerySchema.extend({
|
|
27
|
+
includeDeprecated: z.string().optional(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Deprecate body schema
|
|
31
|
+
const DeprecateBodySchema = z.object({
|
|
32
|
+
message: z.string().optional(),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export async function versionsRoutes(
|
|
36
|
+
server: FastifyInstance,
|
|
37
|
+
_opts: FastifyPluginOptions
|
|
38
|
+
): Promise<void> {
|
|
39
|
+
// Get all versions of a skill
|
|
40
|
+
// GET /v1/skills/:name/versions
|
|
41
|
+
server.get('/:name/versions', {
|
|
42
|
+
preHandler: [
|
|
43
|
+
optionalAuth,
|
|
44
|
+
validateParams(SkillNameParamSchema),
|
|
45
|
+
validateQuery(VersionsQuerySchema),
|
|
46
|
+
],
|
|
47
|
+
handler: getVersions,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Get a specific version
|
|
51
|
+
// GET /v1/skills/:name/versions/:version
|
|
52
|
+
server.get('/:name/versions/:version', {
|
|
53
|
+
preHandler: [
|
|
54
|
+
optionalAuth,
|
|
55
|
+
validateParams(SkillVersionParamSchema),
|
|
56
|
+
],
|
|
57
|
+
handler: getVersion,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Publish a new version
|
|
61
|
+
// POST /v1/skills/:name/versions
|
|
62
|
+
server.post('/:name/versions', {
|
|
63
|
+
preHandler: [
|
|
64
|
+
requireAuth,
|
|
65
|
+
validateParams(SkillNameParamSchema),
|
|
66
|
+
validateBody(PublishVersionBodySchema),
|
|
67
|
+
],
|
|
68
|
+
handler: publishVersion,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Deprecate a version
|
|
72
|
+
// POST /v1/skills/:name/versions/:version/deprecate
|
|
73
|
+
server.post('/:name/versions/:version/deprecate', {
|
|
74
|
+
preHandler: [
|
|
75
|
+
requireAuth,
|
|
76
|
+
validateParams(SkillVersionParamSchema),
|
|
77
|
+
validateBody(DeprecateBodySchema),
|
|
78
|
+
],
|
|
79
|
+
handler: deprecateVersion,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Delete/yank a version
|
|
83
|
+
// DELETE /v1/skills/:name/versions/:version
|
|
84
|
+
server.delete('/:name/versions/:version', {
|
|
85
|
+
preHandler: [
|
|
86
|
+
requireAuth,
|
|
87
|
+
validateParams(SkillVersionParamSchema),
|
|
88
|
+
],
|
|
89
|
+
handler: deleteVersion,
|
|
90
|
+
});
|
|
91
|
+
}
|