@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,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Skills Hub - Request Validation Middleware
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
6
|
+
import { z, ZodSchema, ZodError } from 'zod';
|
|
7
|
+
import { AppError, ErrorCodes } from '@open-skills-hub/core';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Validate request body with Zod schema
|
|
11
|
+
*/
|
|
12
|
+
export function validateBody<T>(schema: ZodSchema<T>) {
|
|
13
|
+
return async function(
|
|
14
|
+
request: FastifyRequest,
|
|
15
|
+
_reply: FastifyReply
|
|
16
|
+
): Promise<void> {
|
|
17
|
+
try {
|
|
18
|
+
request.body = schema.parse(request.body);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (error instanceof ZodError) {
|
|
21
|
+
throw error; // Will be handled by error middleware
|
|
22
|
+
}
|
|
23
|
+
throw new AppError(
|
|
24
|
+
ErrorCodes.VALIDATION_FAILED,
|
|
25
|
+
'Invalid request body',
|
|
26
|
+
400
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Validate request query with Zod schema
|
|
34
|
+
*/
|
|
35
|
+
export function validateQuery<T>(schema: ZodSchema<T>) {
|
|
36
|
+
return async function(
|
|
37
|
+
request: FastifyRequest,
|
|
38
|
+
_reply: FastifyReply
|
|
39
|
+
): Promise<void> {
|
|
40
|
+
try {
|
|
41
|
+
request.query = schema.parse(request.query);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (error instanceof ZodError) {
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
throw new AppError(
|
|
47
|
+
ErrorCodes.VALIDATION_FAILED,
|
|
48
|
+
'Invalid query parameters',
|
|
49
|
+
400
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Validate request params with Zod schema
|
|
57
|
+
*/
|
|
58
|
+
export function validateParams<T>(schema: ZodSchema<T>) {
|
|
59
|
+
return async function(
|
|
60
|
+
request: FastifyRequest,
|
|
61
|
+
_reply: FastifyReply
|
|
62
|
+
): Promise<void> {
|
|
63
|
+
try {
|
|
64
|
+
request.params = schema.parse(request.params);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if (error instanceof ZodError) {
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
throw new AppError(
|
|
70
|
+
ErrorCodes.VALIDATION_FAILED,
|
|
71
|
+
'Invalid path parameters',
|
|
72
|
+
400
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// Common Validation Schemas
|
|
80
|
+
// ============================================================================
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Pagination query schema
|
|
84
|
+
*/
|
|
85
|
+
export const PaginationQuerySchema = z.object({
|
|
86
|
+
cursor: z.string().optional(),
|
|
87
|
+
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
88
|
+
});
|
|
89
|
+
export type PaginationQuery = z.infer<typeof PaginationQuerySchema>;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Skill name param schema
|
|
93
|
+
*/
|
|
94
|
+
export const SkillNameParamSchema = z.object({
|
|
95
|
+
name: z.string().min(1).max(214),
|
|
96
|
+
});
|
|
97
|
+
export type SkillNameParam = z.infer<typeof SkillNameParamSchema>;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Skill name with version param schema
|
|
101
|
+
*/
|
|
102
|
+
export const SkillVersionParamSchema = z.object({
|
|
103
|
+
name: z.string().min(1).max(214),
|
|
104
|
+
version: z.string().regex(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-.+)?$/),
|
|
105
|
+
});
|
|
106
|
+
export type SkillVersionParam = z.infer<typeof SkillVersionParamSchema>;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* ID param schema
|
|
110
|
+
*/
|
|
111
|
+
export const IdParamSchema = z.object({
|
|
112
|
+
id: z.string().min(1),
|
|
113
|
+
});
|
|
114
|
+
export type IdParam = z.infer<typeof IdParamSchema>;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Search query schema
|
|
118
|
+
*/
|
|
119
|
+
export const SearchQuerySchema = z.object({
|
|
120
|
+
q: z.string().optional(),
|
|
121
|
+
category: z.string().optional(),
|
|
122
|
+
author: z.string().optional(),
|
|
123
|
+
sort: z.enum(['relevance', 'uses', 'updated', 'created', 'name']).default('relevance'),
|
|
124
|
+
order: z.enum(['asc', 'desc']).default('desc'),
|
|
125
|
+
cursor: z.string().optional(),
|
|
126
|
+
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
127
|
+
});
|
|
128
|
+
export type SearchQuery = z.infer<typeof SearchQuerySchema>;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Create skill body schema
|
|
132
|
+
*/
|
|
133
|
+
export const CreateSkillBodySchema = z.object({
|
|
134
|
+
name: z.string().regex(/^[a-z][a-z0-9-]*[a-z0-9]$|^[a-z]$/, 'Invalid skill name format'),
|
|
135
|
+
scope: z.string().optional(),
|
|
136
|
+
displayName: z.string().optional(),
|
|
137
|
+
description: z.string().min(1).max(10000),
|
|
138
|
+
category: z.string().optional(),
|
|
139
|
+
keywords: z.array(z.string()).max(20).default([]),
|
|
140
|
+
license: z.string().optional(),
|
|
141
|
+
repository: z.string().url().optional(),
|
|
142
|
+
homepage: z.string().url().optional(),
|
|
143
|
+
visibility: z.enum(['public', 'private', 'unlisted']).default('public'),
|
|
144
|
+
content: z.object({
|
|
145
|
+
frontmatter: z.object({
|
|
146
|
+
name: z.string(),
|
|
147
|
+
description: z.string().optional(),
|
|
148
|
+
author: z.string().optional(), // Author field for skill attribution
|
|
149
|
+
allowedTools: z.array(z.string()).optional(),
|
|
150
|
+
argumentHint: z.string().optional(),
|
|
151
|
+
disableModelInvocation: z.boolean().optional(),
|
|
152
|
+
userInvocable: z.boolean().optional(),
|
|
153
|
+
model: z.string().optional(),
|
|
154
|
+
context: z.literal('fork').optional(),
|
|
155
|
+
agent: z.string().optional(),
|
|
156
|
+
license: z.string().optional(),
|
|
157
|
+
compatibility: z.string().optional(),
|
|
158
|
+
derivedFrom: z.object({
|
|
159
|
+
name: z.string(),
|
|
160
|
+
version: z.string(),
|
|
161
|
+
author: z.string(),
|
|
162
|
+
}).optional(),
|
|
163
|
+
metadata: z.record(z.string()).optional(),
|
|
164
|
+
}),
|
|
165
|
+
markdown: z.string().min(1),
|
|
166
|
+
files: z.array(z.object({
|
|
167
|
+
path: z.string(),
|
|
168
|
+
content: z.string(),
|
|
169
|
+
size: z.number(),
|
|
170
|
+
})).optional(),
|
|
171
|
+
}),
|
|
172
|
+
version: z.string().regex(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-.+)?$/).default('1.0.0'),
|
|
173
|
+
changelog: z.string().optional(),
|
|
174
|
+
});
|
|
175
|
+
export type CreateSkillBody = z.infer<typeof CreateSkillBodySchema>;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Update skill body schema
|
|
179
|
+
*/
|
|
180
|
+
export const UpdateSkillBodySchema = z.object({
|
|
181
|
+
displayName: z.string().optional(),
|
|
182
|
+
description: z.string().min(1).max(10000).optional(),
|
|
183
|
+
category: z.string().optional(),
|
|
184
|
+
keywords: z.array(z.string()).max(20).optional(),
|
|
185
|
+
repository: z.string().url().optional().nullable(),
|
|
186
|
+
homepage: z.string().url().optional().nullable(),
|
|
187
|
+
visibility: z.enum(['public', 'private', 'unlisted']).optional(),
|
|
188
|
+
});
|
|
189
|
+
export type UpdateSkillBody = z.infer<typeof UpdateSkillBodySchema>;
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Publish version body schema
|
|
193
|
+
*/
|
|
194
|
+
export const PublishVersionBodySchema = z.object({
|
|
195
|
+
version: z.string().regex(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-.+)?$/),
|
|
196
|
+
content: z.object({
|
|
197
|
+
frontmatter: z.object({
|
|
198
|
+
name: z.string(),
|
|
199
|
+
description: z.string().optional(),
|
|
200
|
+
allowedTools: z.array(z.string()).optional(),
|
|
201
|
+
argumentHint: z.string().optional(),
|
|
202
|
+
disableModelInvocation: z.boolean().optional(),
|
|
203
|
+
userInvocable: z.boolean().optional(),
|
|
204
|
+
model: z.string().optional(),
|
|
205
|
+
context: z.literal('fork').optional(),
|
|
206
|
+
agent: z.string().optional(),
|
|
207
|
+
license: z.string().optional(),
|
|
208
|
+
compatibility: z.string().optional(),
|
|
209
|
+
derivedFrom: z.object({
|
|
210
|
+
name: z.string(),
|
|
211
|
+
version: z.string(),
|
|
212
|
+
author: z.string(),
|
|
213
|
+
}).optional(),
|
|
214
|
+
metadata: z.record(z.string()).optional(),
|
|
215
|
+
}),
|
|
216
|
+
markdown: z.string().min(1),
|
|
217
|
+
files: z.array(z.object({
|
|
218
|
+
path: z.string(),
|
|
219
|
+
content: z.string(),
|
|
220
|
+
size: z.number(),
|
|
221
|
+
})).optional(),
|
|
222
|
+
}),
|
|
223
|
+
changelog: z.string().optional(),
|
|
224
|
+
tag: z.string().optional(),
|
|
225
|
+
});
|
|
226
|
+
export type PublishVersionBody = z.infer<typeof PublishVersionBodySchema>;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Submit feedback body schema
|
|
230
|
+
*/
|
|
231
|
+
export const SubmitFeedbackBodySchema = z.object({
|
|
232
|
+
skillName: z.string(),
|
|
233
|
+
skillVersion: z.string(),
|
|
234
|
+
feedbackType: z.enum(['success', 'failure', 'suggestion', 'bug']),
|
|
235
|
+
rating: z.number().min(1).max(5),
|
|
236
|
+
comment: z.string().max(5000).optional(),
|
|
237
|
+
context: z.object({
|
|
238
|
+
executionTime: z.number().optional(),
|
|
239
|
+
taskType: z.string().optional(),
|
|
240
|
+
errorMessage: z.string().optional(),
|
|
241
|
+
agentType: z.string().optional(),
|
|
242
|
+
agentVersion: z.string().optional(),
|
|
243
|
+
outcome: z.enum(['success', 'partial', 'failure']).optional(),
|
|
244
|
+
}).optional(),
|
|
245
|
+
});
|
|
246
|
+
export type SubmitFeedbackBody = z.infer<typeof SubmitFeedbackBodySchema>;
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Scan content body schema
|
|
250
|
+
*/
|
|
251
|
+
export const ScanContentBodySchema = z.object({
|
|
252
|
+
content: z.string().min(1).max(1024 * 1024), // 1MB max
|
|
253
|
+
contentType: z.enum(['markdown', 'yaml', 'json']).default('markdown'),
|
|
254
|
+
});
|
|
255
|
+
export type ScanContentBody = z.infer<typeof ScanContentBodySchema>;
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Audit query schema
|
|
259
|
+
*/
|
|
260
|
+
export const AuditQuerySchema = z.object({
|
|
261
|
+
eventType: z.string().optional(),
|
|
262
|
+
actor: z.string().optional(),
|
|
263
|
+
resourceType: z.string().optional(),
|
|
264
|
+
resourceName: z.string().optional(),
|
|
265
|
+
from: z.string().optional(),
|
|
266
|
+
to: z.string().optional(),
|
|
267
|
+
cursor: z.string().optional(),
|
|
268
|
+
limit: z.coerce.number().int().min(1).max(100).default(50),
|
|
269
|
+
});
|
|
270
|
+
export type AuditQuery = z.infer<typeof AuditQuerySchema>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Skills Hub - Audit Routes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import {
|
|
8
|
+
getAuditLogs,
|
|
9
|
+
getSkillAuditLogs,
|
|
10
|
+
getAuditLogById,
|
|
11
|
+
getAuditStats,
|
|
12
|
+
} from '../controllers/audit.js';
|
|
13
|
+
import { requireAuth, requirePermission } from '../middleware/auth.js';
|
|
14
|
+
import {
|
|
15
|
+
validateParams,
|
|
16
|
+
validateQuery,
|
|
17
|
+
SkillNameParamSchema,
|
|
18
|
+
IdParamSchema,
|
|
19
|
+
AuditQuerySchema,
|
|
20
|
+
} from '../middleware/validation.js';
|
|
21
|
+
|
|
22
|
+
// Stats query schema
|
|
23
|
+
const StatsQuerySchema = z.object({
|
|
24
|
+
days: z.coerce.number().int().min(1).max(365).default(30),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export async function auditRoutes(
|
|
28
|
+
server: FastifyInstance,
|
|
29
|
+
_opts: FastifyPluginOptions
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
// Get audit logs (admin only)
|
|
32
|
+
// GET /v1/audit
|
|
33
|
+
server.get('/', {
|
|
34
|
+
preHandler: [
|
|
35
|
+
requireAuth,
|
|
36
|
+
requirePermission('admin'),
|
|
37
|
+
validateQuery(AuditQuerySchema),
|
|
38
|
+
],
|
|
39
|
+
handler: getAuditLogs,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Get audit statistics
|
|
43
|
+
// GET /v1/audit/stats
|
|
44
|
+
server.get('/stats', {
|
|
45
|
+
preHandler: [
|
|
46
|
+
requireAuth,
|
|
47
|
+
requirePermission('admin'),
|
|
48
|
+
validateQuery(StatsQuerySchema),
|
|
49
|
+
],
|
|
50
|
+
handler: getAuditStats,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Get audit logs for a specific skill
|
|
54
|
+
// GET /v1/audit/skill/:name
|
|
55
|
+
server.get('/skill/:name', {
|
|
56
|
+
preHandler: [
|
|
57
|
+
requireAuth,
|
|
58
|
+
validateParams(SkillNameParamSchema),
|
|
59
|
+
validateQuery(AuditQuerySchema),
|
|
60
|
+
],
|
|
61
|
+
handler: getSkillAuditLogs,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Get audit log by ID
|
|
65
|
+
// GET /v1/audit/:id
|
|
66
|
+
server.get('/:id', {
|
|
67
|
+
preHandler: [
|
|
68
|
+
requireAuth,
|
|
69
|
+
requirePermission('admin'),
|
|
70
|
+
validateParams(IdParamSchema),
|
|
71
|
+
],
|
|
72
|
+
handler: getAuditLogById,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Skills Hub - Cache Routes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
|
6
|
+
import {
|
|
7
|
+
getCachedSkills,
|
|
8
|
+
getSkillCacheInfo,
|
|
9
|
+
getVersionCacheInfo,
|
|
10
|
+
invalidateSkillCache,
|
|
11
|
+
invalidateVersionCache,
|
|
12
|
+
cleanExpiredCache,
|
|
13
|
+
getCacheStats,
|
|
14
|
+
} from '../controllers/cache.js';
|
|
15
|
+
import { optionalAuth, requireAuth, requirePermission } from '../middleware/auth.js';
|
|
16
|
+
import {
|
|
17
|
+
validateParams,
|
|
18
|
+
SkillNameParamSchema,
|
|
19
|
+
SkillVersionParamSchema,
|
|
20
|
+
} from '../middleware/validation.js';
|
|
21
|
+
|
|
22
|
+
export async function cacheRoutes(
|
|
23
|
+
server: FastifyInstance,
|
|
24
|
+
_opts: FastifyPluginOptions
|
|
25
|
+
): Promise<void> {
|
|
26
|
+
// Get all cached skills
|
|
27
|
+
// GET /v1/cache
|
|
28
|
+
server.get('/', {
|
|
29
|
+
preHandler: [
|
|
30
|
+
requireAuth,
|
|
31
|
+
],
|
|
32
|
+
handler: getCachedSkills,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Get cache statistics
|
|
36
|
+
// GET /v1/cache/stats
|
|
37
|
+
server.get('/stats', {
|
|
38
|
+
preHandler: [
|
|
39
|
+
requireAuth,
|
|
40
|
+
],
|
|
41
|
+
handler: getCacheStats,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Clean expired cache entries
|
|
45
|
+
// POST /v1/cache/clean
|
|
46
|
+
server.post('/clean', {
|
|
47
|
+
preHandler: [
|
|
48
|
+
requireAuth,
|
|
49
|
+
requirePermission('admin'),
|
|
50
|
+
],
|
|
51
|
+
handler: cleanExpiredCache,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Get cache info for a skill
|
|
55
|
+
// GET /v1/cache/:name
|
|
56
|
+
server.get('/:name', {
|
|
57
|
+
preHandler: [
|
|
58
|
+
optionalAuth,
|
|
59
|
+
validateParams(SkillNameParamSchema),
|
|
60
|
+
],
|
|
61
|
+
handler: getSkillCacheInfo,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Invalidate cache for a skill
|
|
65
|
+
// DELETE /v1/cache/:name
|
|
66
|
+
server.delete('/:name', {
|
|
67
|
+
preHandler: [
|
|
68
|
+
requireAuth,
|
|
69
|
+
validateParams(SkillNameParamSchema),
|
|
70
|
+
],
|
|
71
|
+
handler: invalidateSkillCache,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Get cache info for a specific version
|
|
75
|
+
// GET /v1/cache/:name/:version
|
|
76
|
+
server.get('/:name/:version', {
|
|
77
|
+
preHandler: [
|
|
78
|
+
optionalAuth,
|
|
79
|
+
validateParams(SkillVersionParamSchema),
|
|
80
|
+
],
|
|
81
|
+
handler: getVersionCacheInfo,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Invalidate cache for a specific version
|
|
85
|
+
// DELETE /v1/cache/:name/:version
|
|
86
|
+
server.delete('/:name/:version', {
|
|
87
|
+
preHandler: [
|
|
88
|
+
requireAuth,
|
|
89
|
+
validateParams(SkillVersionParamSchema),
|
|
90
|
+
],
|
|
91
|
+
handler: invalidateVersionCache,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Skills Hub - Feedback Routes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import {
|
|
8
|
+
submitFeedback,
|
|
9
|
+
getSkillFeedbacks,
|
|
10
|
+
getFeedbackById,
|
|
11
|
+
reviewFeedback,
|
|
12
|
+
getFeedbackStats,
|
|
13
|
+
} from '../controllers/feedback.js';
|
|
14
|
+
import { optionalAuth, requireAuth, requirePermission } from '../middleware/auth.js';
|
|
15
|
+
import {
|
|
16
|
+
validateBody,
|
|
17
|
+
validateParams,
|
|
18
|
+
validateQuery,
|
|
19
|
+
SkillNameParamSchema,
|
|
20
|
+
IdParamSchema,
|
|
21
|
+
SubmitFeedbackBodySchema,
|
|
22
|
+
PaginationQuerySchema,
|
|
23
|
+
} from '../middleware/validation.js';
|
|
24
|
+
|
|
25
|
+
// Extended query schema for feedbacks
|
|
26
|
+
const FeedbackQuerySchema = PaginationQuerySchema.extend({
|
|
27
|
+
feedbackType: z.enum(['success', 'failure', 'suggestion', 'bug']).optional(),
|
|
28
|
+
status: z.enum(['pending', 'reviewed', 'accepted', 'rejected']).optional(),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Review feedback body schema
|
|
32
|
+
const ReviewFeedbackBodySchema = z.object({
|
|
33
|
+
status: z.enum(['reviewed', 'accepted', 'rejected']),
|
|
34
|
+
notes: z.string().optional(),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export async function feedbackRoutes(
|
|
38
|
+
server: FastifyInstance,
|
|
39
|
+
_opts: FastifyPluginOptions
|
|
40
|
+
): Promise<void> {
|
|
41
|
+
// Submit feedback
|
|
42
|
+
// POST /v1/feedback
|
|
43
|
+
server.post('/', {
|
|
44
|
+
preHandler: [
|
|
45
|
+
optionalAuth,
|
|
46
|
+
validateBody(SubmitFeedbackBodySchema),
|
|
47
|
+
],
|
|
48
|
+
handler: submitFeedback,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Get feedbacks for a skill
|
|
52
|
+
// GET /v1/feedback/skill/:name
|
|
53
|
+
server.get('/skill/:name', {
|
|
54
|
+
preHandler: [
|
|
55
|
+
optionalAuth,
|
|
56
|
+
validateParams(SkillNameParamSchema),
|
|
57
|
+
validateQuery(FeedbackQuerySchema),
|
|
58
|
+
],
|
|
59
|
+
handler: getSkillFeedbacks,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Get feedback statistics for a skill
|
|
63
|
+
// GET /v1/feedback/skill/:name/stats
|
|
64
|
+
server.get('/skill/:name/stats', {
|
|
65
|
+
preHandler: [
|
|
66
|
+
optionalAuth,
|
|
67
|
+
validateParams(SkillNameParamSchema),
|
|
68
|
+
],
|
|
69
|
+
handler: getFeedbackStats,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Get feedback by ID
|
|
73
|
+
// GET /v1/feedback/:id
|
|
74
|
+
server.get('/:id', {
|
|
75
|
+
preHandler: [
|
|
76
|
+
optionalAuth,
|
|
77
|
+
validateParams(IdParamSchema),
|
|
78
|
+
],
|
|
79
|
+
handler: getFeedbackById,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Review feedback (admin only)
|
|
83
|
+
// POST /v1/feedback/:id/review
|
|
84
|
+
server.post('/:id/review', {
|
|
85
|
+
preHandler: [
|
|
86
|
+
requireAuth,
|
|
87
|
+
requirePermission('admin'),
|
|
88
|
+
validateParams(IdParamSchema),
|
|
89
|
+
validateBody(ReviewFeedbackBodySchema),
|
|
90
|
+
],
|
|
91
|
+
handler: reviewFeedback,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Skills Hub - Health Check Routes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { FastifyInstance, FastifyPluginOptions, FastifyRequest, FastifyReply } from 'fastify';
|
|
6
|
+
import { getStorage, getConfig } from '@open-skills-hub/core';
|
|
7
|
+
import { sendSuccess } from '../middleware/error.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Basic health check
|
|
11
|
+
*/
|
|
12
|
+
async function healthCheck(
|
|
13
|
+
request: FastifyRequest,
|
|
14
|
+
reply: FastifyReply
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
sendSuccess(request, reply, {
|
|
17
|
+
status: 'ok',
|
|
18
|
+
timestamp: new Date().toISOString(),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Readiness check (includes storage health)
|
|
24
|
+
*/
|
|
25
|
+
async function readinessCheck(
|
|
26
|
+
request: FastifyRequest,
|
|
27
|
+
reply: FastifyReply
|
|
28
|
+
): Promise<void> {
|
|
29
|
+
const storage = await getStorage();
|
|
30
|
+
const config = getConfig().get();
|
|
31
|
+
|
|
32
|
+
const storageHealthy = await storage.healthCheck();
|
|
33
|
+
|
|
34
|
+
if (!storageHealthy) {
|
|
35
|
+
reply.status(503).send({
|
|
36
|
+
success: false,
|
|
37
|
+
error: {
|
|
38
|
+
code: 'SERVICE_UNAVAILABLE',
|
|
39
|
+
message: 'Storage is not available',
|
|
40
|
+
},
|
|
41
|
+
meta: {
|
|
42
|
+
requestId: request.id as string,
|
|
43
|
+
timestamp: new Date().toISOString(),
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
sendSuccess(request, reply, {
|
|
50
|
+
status: 'ready',
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
checks: {
|
|
53
|
+
storage: storageHealthy ? 'healthy' : 'unhealthy',
|
|
54
|
+
},
|
|
55
|
+
config: {
|
|
56
|
+
mode: config.mode,
|
|
57
|
+
version: '1.0.0',
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Detailed system info (admin only)
|
|
64
|
+
*/
|
|
65
|
+
async function systemInfo(
|
|
66
|
+
request: FastifyRequest,
|
|
67
|
+
reply: FastifyReply
|
|
68
|
+
): Promise<void> {
|
|
69
|
+
const storage = await getStorage();
|
|
70
|
+
const config = getConfig();
|
|
71
|
+
|
|
72
|
+
const storageHealthy = await storage.healthCheck();
|
|
73
|
+
|
|
74
|
+
sendSuccess(request, reply, {
|
|
75
|
+
status: 'ok',
|
|
76
|
+
timestamp: new Date().toISOString(),
|
|
77
|
+
system: {
|
|
78
|
+
nodeVersion: process.version,
|
|
79
|
+
platform: process.platform,
|
|
80
|
+
arch: process.arch,
|
|
81
|
+
uptime: process.uptime(),
|
|
82
|
+
memoryUsage: process.memoryUsage(),
|
|
83
|
+
},
|
|
84
|
+
config: {
|
|
85
|
+
mode: config.get().mode,
|
|
86
|
+
storagePath: config.getStoragePath(),
|
|
87
|
+
serverPort: config.get().server.port,
|
|
88
|
+
authRequired: config.get().auth.requireAuth,
|
|
89
|
+
scannerEnabled: config.get().scanner.enabled,
|
|
90
|
+
rateLimitEnabled: config.get().rateLimit.enabled,
|
|
91
|
+
cacheTTL: config.get().cache.ttl,
|
|
92
|
+
},
|
|
93
|
+
checks: {
|
|
94
|
+
storage: storageHealthy ? 'healthy' : 'unhealthy',
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Reload database from disk
|
|
101
|
+
* Useful when CLI or external processes have updated the database
|
|
102
|
+
*/
|
|
103
|
+
async function reloadDatabase(
|
|
104
|
+
request: FastifyRequest,
|
|
105
|
+
reply: FastifyReply
|
|
106
|
+
): Promise<void> {
|
|
107
|
+
const storage = await getStorage();
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
await storage.reload();
|
|
111
|
+
sendSuccess(request, reply, {
|
|
112
|
+
status: 'reloaded',
|
|
113
|
+
timestamp: new Date().toISOString(),
|
|
114
|
+
message: 'Database reloaded successfully from disk',
|
|
115
|
+
});
|
|
116
|
+
} catch (error) {
|
|
117
|
+
reply.status(500).send({
|
|
118
|
+
success: false,
|
|
119
|
+
error: {
|
|
120
|
+
code: 'RELOAD_FAILED',
|
|
121
|
+
message: 'Failed to reload database',
|
|
122
|
+
details: error instanceof Error ? error.message : String(error),
|
|
123
|
+
},
|
|
124
|
+
meta: {
|
|
125
|
+
requestId: request.id as string,
|
|
126
|
+
timestamp: new Date().toISOString(),
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function healthRoutes(
|
|
133
|
+
server: FastifyInstance,
|
|
134
|
+
_opts: FastifyPluginOptions
|
|
135
|
+
): Promise<void> {
|
|
136
|
+
// Basic health check
|
|
137
|
+
// GET /health
|
|
138
|
+
server.get('/health', healthCheck);
|
|
139
|
+
|
|
140
|
+
// Readiness check (for k8s)
|
|
141
|
+
// GET /ready
|
|
142
|
+
server.get('/ready', readinessCheck);
|
|
143
|
+
|
|
144
|
+
// System info (admin)
|
|
145
|
+
// GET /system
|
|
146
|
+
server.get('/system', systemInfo);
|
|
147
|
+
|
|
148
|
+
// Reload database from disk
|
|
149
|
+
// POST /reload
|
|
150
|
+
server.post('/reload', reloadDatabase);
|
|
151
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Skills Hub - Routes Exports
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export * from './skills.js';
|
|
6
|
+
export * from './versions.js';
|
|
7
|
+
export * from './search.js';
|
|
8
|
+
export * from './scan.js';
|
|
9
|
+
export * from './feedback.js';
|
|
10
|
+
export * from './audit.js';
|
|
11
|
+
export * from './cache.js';
|
|
12
|
+
export * from './health.js';
|