@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.
Files changed (112) hide show
  1. package/dist/controllers/audit.d.ts +33 -0
  2. package/dist/controllers/audit.d.ts.map +1 -0
  3. package/dist/controllers/audit.js +122 -0
  4. package/dist/controllers/audit.js.map +1 -0
  5. package/dist/controllers/cache.d.ts +42 -0
  6. package/dist/controllers/cache.d.ts.map +1 -0
  7. package/dist/controllers/cache.js +247 -0
  8. package/dist/controllers/cache.js.map +1 -0
  9. package/dist/controllers/feedback.d.ts +44 -0
  10. package/dist/controllers/feedback.d.ts.map +1 -0
  11. package/dist/controllers/feedback.js +216 -0
  12. package/dist/controllers/feedback.js.map +1 -0
  13. package/dist/controllers/index.d.ts +9 -0
  14. package/dist/controllers/index.d.ts.map +1 -0
  15. package/dist/controllers/index.js +9 -0
  16. package/dist/controllers/index.js.map +1 -0
  17. package/dist/controllers/skills.d.ts +66 -0
  18. package/dist/controllers/skills.d.ts.map +1 -0
  19. package/dist/controllers/skills.js +355 -0
  20. package/dist/controllers/skills.js.map +1 -0
  21. package/dist/controllers/versions.d.ts +43 -0
  22. package/dist/controllers/versions.d.ts.map +1 -0
  23. package/dist/controllers/versions.js +298 -0
  24. package/dist/controllers/versions.js.map +1 -0
  25. package/dist/index.d.ts +9 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +78 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/middleware/auth.d.ts +34 -0
  30. package/dist/middleware/auth.d.ts.map +1 -0
  31. package/dist/middleware/auth.js +148 -0
  32. package/dist/middleware/auth.js.map +1 -0
  33. package/dist/middleware/error.d.ts +26 -0
  34. package/dist/middleware/error.d.ts.map +1 -0
  35. package/dist/middleware/error.js +102 -0
  36. package/dist/middleware/error.js.map +1 -0
  37. package/dist/middleware/index.d.ts +8 -0
  38. package/dist/middleware/index.d.ts.map +1 -0
  39. package/dist/middleware/index.js +8 -0
  40. package/dist/middleware/index.js.map +1 -0
  41. package/dist/middleware/logger.d.ts +19 -0
  42. package/dist/middleware/logger.d.ts.map +1 -0
  43. package/dist/middleware/logger.js +54 -0
  44. package/dist/middleware/logger.js.map +1 -0
  45. package/dist/middleware/validation.d.ts +671 -0
  46. package/dist/middleware/validation.d.ts.map +1 -0
  47. package/dist/middleware/validation.js +225 -0
  48. package/dist/middleware/validation.js.map +1 -0
  49. package/dist/routes/audit.d.ts +6 -0
  50. package/dist/routes/audit.d.ts.map +1 -0
  51. package/dist/routes/audit.js +54 -0
  52. package/dist/routes/audit.js.map +1 -0
  53. package/dist/routes/cache.d.ts +6 -0
  54. package/dist/routes/cache.d.ts.map +1 -0
  55. package/dist/routes/cache.js +70 -0
  56. package/dist/routes/cache.js.map +1 -0
  57. package/dist/routes/feedback.d.ts +6 -0
  58. package/dist/routes/feedback.d.ts.map +1 -0
  59. package/dist/routes/feedback.js +68 -0
  60. package/dist/routes/feedback.js.map +1 -0
  61. package/dist/routes/health.d.ts +6 -0
  62. package/dist/routes/health.d.ts.map +1 -0
  63. package/dist/routes/health.js +122 -0
  64. package/dist/routes/health.js.map +1 -0
  65. package/dist/routes/index.d.ts +12 -0
  66. package/dist/routes/index.d.ts.map +1 -0
  67. package/dist/routes/index.js +12 -0
  68. package/dist/routes/index.js.map +1 -0
  69. package/dist/routes/scan.d.ts +8 -0
  70. package/dist/routes/scan.d.ts.map +1 -0
  71. package/dist/routes/scan.js +315 -0
  72. package/dist/routes/scan.js.map +1 -0
  73. package/dist/routes/search.d.ts +6 -0
  74. package/dist/routes/search.d.ts.map +1 -0
  75. package/dist/routes/search.js +44 -0
  76. package/dist/routes/search.js.map +1 -0
  77. package/dist/routes/skills.d.ts +6 -0
  78. package/dist/routes/skills.d.ts.map +1 -0
  79. package/dist/routes/skills.js +74 -0
  80. package/dist/routes/skills.js.map +1 -0
  81. package/dist/routes/versions.d.ts +6 -0
  82. package/dist/routes/versions.d.ts.map +1 -0
  83. package/dist/routes/versions.js +66 -0
  84. package/dist/routes/versions.js.map +1 -0
  85. package/dist/server.d.ts +26 -0
  86. package/dist/server.d.ts.map +1 -0
  87. package/dist/server.js +166 -0
  88. package/dist/server.js.map +1 -0
  89. package/package.json +42 -0
  90. package/src/controllers/audit.ts +175 -0
  91. package/src/controllers/cache.ts +344 -0
  92. package/src/controllers/feedback.ts +309 -0
  93. package/src/controllers/index.ts +9 -0
  94. package/src/controllers/skills.ts +489 -0
  95. package/src/controllers/versions.ts +427 -0
  96. package/src/index.ts +87 -0
  97. package/src/middleware/auth.ts +219 -0
  98. package/src/middleware/error.ts +180 -0
  99. package/src/middleware/index.ts +8 -0
  100. package/src/middleware/logger.ts +71 -0
  101. package/src/middleware/validation.ts +270 -0
  102. package/src/routes/audit.ts +74 -0
  103. package/src/routes/cache.ts +93 -0
  104. package/src/routes/feedback.ts +93 -0
  105. package/src/routes/health.ts +151 -0
  106. package/src/routes/index.ts +12 -0
  107. package/src/routes/scan.ts +428 -0
  108. package/src/routes/search.ts +51 -0
  109. package/src/routes/skills.ts +102 -0
  110. package/src/routes/versions.ts +91 -0
  111. package/src/server.ts +205 -0
  112. 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';