@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,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Skills Hub - Authentication Middleware
|
|
3
|
+
*/
|
|
4
|
+
import * as crypto from 'crypto';
|
|
5
|
+
import { getConfig, AppError, ErrorCodes, } from '@open-skills-hub/core';
|
|
6
|
+
import { logger } from './logger.js';
|
|
7
|
+
/**
|
|
8
|
+
* Extract Bearer token from Authorization header
|
|
9
|
+
*/
|
|
10
|
+
function extractBearerToken(request) {
|
|
11
|
+
const authHeader = request.headers.authorization;
|
|
12
|
+
if (!authHeader)
|
|
13
|
+
return null;
|
|
14
|
+
const parts = authHeader.split(' ');
|
|
15
|
+
if (parts.length !== 2 || parts[0]?.toLowerCase() !== 'bearer') {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return parts[1] ?? null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Verify JWT token (simplified - in production use jsonwebtoken library)
|
|
22
|
+
*/
|
|
23
|
+
function verifyToken(token, secret) {
|
|
24
|
+
try {
|
|
25
|
+
// Simple JWT verification (header.payload.signature)
|
|
26
|
+
const parts = token.split('.');
|
|
27
|
+
if (parts.length !== 3)
|
|
28
|
+
return null;
|
|
29
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
30
|
+
// Verify signature
|
|
31
|
+
const data = `${headerB64}.${payloadB64}`;
|
|
32
|
+
const expectedSignature = crypto
|
|
33
|
+
.createHmac('sha256', secret)
|
|
34
|
+
.update(data)
|
|
35
|
+
.digest('base64url');
|
|
36
|
+
if (signatureB64 !== expectedSignature) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
// Decode payload
|
|
40
|
+
const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString());
|
|
41
|
+
// Check expiration
|
|
42
|
+
if (payload.exp && Date.now() / 1000 > payload.exp) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
id: payload.sub,
|
|
47
|
+
username: payload.username,
|
|
48
|
+
type: payload.type || 'user',
|
|
49
|
+
permissions: payload.permissions || [],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Authentication middleware - requires valid token
|
|
58
|
+
*/
|
|
59
|
+
export async function requireAuth(request, reply) {
|
|
60
|
+
const config = getConfig().get();
|
|
61
|
+
// Skip auth if not required
|
|
62
|
+
if (!config.auth.requireAuth) {
|
|
63
|
+
// Set a default system user for unauthenticated requests
|
|
64
|
+
request.user = {
|
|
65
|
+
id: 'anonymous',
|
|
66
|
+
type: 'user',
|
|
67
|
+
permissions: ['read'],
|
|
68
|
+
};
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const token = extractBearerToken(request);
|
|
72
|
+
if (!token) {
|
|
73
|
+
throw new AppError(ErrorCodes.UNAUTHORIZED, 'Authentication required', 401);
|
|
74
|
+
}
|
|
75
|
+
const secret = config.auth.jwtSecret;
|
|
76
|
+
if (!secret) {
|
|
77
|
+
logger.error('JWT secret not configured');
|
|
78
|
+
throw new AppError(ErrorCodes.INTERNAL_ERROR, 'Authentication not configured', 500);
|
|
79
|
+
}
|
|
80
|
+
const user = verifyToken(token, secret);
|
|
81
|
+
if (!user) {
|
|
82
|
+
throw new AppError(ErrorCodes.TOKEN_INVALID, 'Invalid or expired token', 401);
|
|
83
|
+
}
|
|
84
|
+
request.user = user;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Optional authentication - attaches user if token provided
|
|
88
|
+
*/
|
|
89
|
+
export async function optionalAuth(request, _reply) {
|
|
90
|
+
const config = getConfig().get();
|
|
91
|
+
const token = extractBearerToken(request);
|
|
92
|
+
if (!token) {
|
|
93
|
+
request.user = {
|
|
94
|
+
id: 'anonymous',
|
|
95
|
+
type: 'user',
|
|
96
|
+
permissions: ['read'],
|
|
97
|
+
};
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const secret = config.auth.jwtSecret;
|
|
101
|
+
if (!secret) {
|
|
102
|
+
request.user = {
|
|
103
|
+
id: 'anonymous',
|
|
104
|
+
type: 'user',
|
|
105
|
+
permissions: ['read'],
|
|
106
|
+
};
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const user = verifyToken(token, secret);
|
|
110
|
+
request.user = user ?? {
|
|
111
|
+
id: 'anonymous',
|
|
112
|
+
type: 'user',
|
|
113
|
+
permissions: ['read'],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Permission check middleware
|
|
118
|
+
*/
|
|
119
|
+
export function requirePermission(permission) {
|
|
120
|
+
return async function (request, _reply) {
|
|
121
|
+
if (!request.user) {
|
|
122
|
+
throw new AppError(ErrorCodes.UNAUTHORIZED, 'Authentication required', 401);
|
|
123
|
+
}
|
|
124
|
+
if (!request.user.permissions.includes(permission) && !request.user.permissions.includes('admin')) {
|
|
125
|
+
throw new AppError(ErrorCodes.INSUFFICIENT_PERMISSIONS, `Permission '${permission}' required`, 403);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Generate a simple JWT token (for testing)
|
|
131
|
+
*/
|
|
132
|
+
export function generateToken(user, secret, expiresIn = 86400) {
|
|
133
|
+
const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
|
|
134
|
+
const payload = Buffer.from(JSON.stringify({
|
|
135
|
+
sub: user.id,
|
|
136
|
+
username: user.username,
|
|
137
|
+
type: user.type,
|
|
138
|
+
permissions: user.permissions || [],
|
|
139
|
+
iat: Math.floor(Date.now() / 1000),
|
|
140
|
+
exp: Math.floor(Date.now() / 1000) + expiresIn,
|
|
141
|
+
})).toString('base64url');
|
|
142
|
+
const signature = crypto
|
|
143
|
+
.createHmac('sha256', secret)
|
|
144
|
+
.update(`${header}.${payload}`)
|
|
145
|
+
.digest('base64url');
|
|
146
|
+
return `${header}.${payload}.${signature}`;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Skills Hub - Error Handling Middleware
|
|
3
|
+
*/
|
|
4
|
+
import type { FastifyRequest, FastifyReply, FastifyError } from 'fastify';
|
|
5
|
+
import type { ApiResponse, ApiError } from '@open-skills-hub/core';
|
|
6
|
+
/**
|
|
7
|
+
* Build standard API response
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildApiResponse<T>(request: FastifyRequest, data?: T, error?: ApiError, pagination?: ApiResponse['pagination']): ApiResponse<T>;
|
|
10
|
+
/**
|
|
11
|
+
* Send success response
|
|
12
|
+
*/
|
|
13
|
+
export declare function sendSuccess<T>(request: FastifyRequest, reply: FastifyReply, data: T, statusCode?: number, pagination?: ApiResponse['pagination']): void;
|
|
14
|
+
/**
|
|
15
|
+
* Send error response
|
|
16
|
+
*/
|
|
17
|
+
export declare function sendError(request: FastifyRequest, reply: FastifyReply, code: string, message: string, statusCode?: number, details?: Record<string, unknown>): void;
|
|
18
|
+
/**
|
|
19
|
+
* Global error handler
|
|
20
|
+
*/
|
|
21
|
+
export declare function errorHandler(error: FastifyError | Error, request: FastifyRequest, reply: FastifyReply): void;
|
|
22
|
+
/**
|
|
23
|
+
* Not found handler
|
|
24
|
+
*/
|
|
25
|
+
export declare function notFoundHandler(request: FastifyRequest, reply: FastifyReply): void;
|
|
26
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/middleware/error.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAQ1E,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGnE;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,OAAO,EAAE,cAAc,EACvB,IAAI,CAAC,EAAE,CAAC,EACR,KAAK,CAAC,EAAE,QAAQ,EAChB,UAAU,CAAC,EAAE,WAAW,CAAC,YAAY,CAAC,GACrC,WAAW,CAAC,CAAC,CAAC,CAWhB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAC3B,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,CAAC,EACP,UAAU,GAAE,MAAY,EACxB,UAAU,CAAC,EAAE,WAAW,CAAC,YAAY,CAAC,GACrC,IAAI,CAEN;AAED;;GAEG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,MAAY,EACxB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI,CAIN;AAqBD;;GAEG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,YAAY,GAAG,KAAK,EAC3B,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,YAAY,GAClB,IAAI,CAwEN;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,YAAY,GAClB,IAAI,CAQN"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Skills Hub - Error Handling Middleware
|
|
3
|
+
*/
|
|
4
|
+
import { ZodError } from 'zod';
|
|
5
|
+
import { AppError, ErrorCodes, now, } from '@open-skills-hub/core';
|
|
6
|
+
import { logger } from './logger.js';
|
|
7
|
+
/**
|
|
8
|
+
* Build standard API response
|
|
9
|
+
*/
|
|
10
|
+
export function buildApiResponse(request, data, error, pagination) {
|
|
11
|
+
return {
|
|
12
|
+
success: !error,
|
|
13
|
+
data,
|
|
14
|
+
error,
|
|
15
|
+
pagination,
|
|
16
|
+
meta: {
|
|
17
|
+
requestId: request.id,
|
|
18
|
+
timestamp: now(),
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Send success response
|
|
24
|
+
*/
|
|
25
|
+
export function sendSuccess(request, reply, data, statusCode = 200, pagination) {
|
|
26
|
+
reply.status(statusCode).send(buildApiResponse(request, data, undefined, pagination));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Send error response
|
|
30
|
+
*/
|
|
31
|
+
export function sendError(request, reply, code, message, statusCode = 500, details) {
|
|
32
|
+
reply.status(statusCode).send(buildApiResponse(request, undefined, { code, message, details }));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Convert Zod validation error to API error format
|
|
36
|
+
*/
|
|
37
|
+
function formatZodError(error) {
|
|
38
|
+
const issues = error.issues.map(issue => ({
|
|
39
|
+
path: issue.path.join('.'),
|
|
40
|
+
message: issue.message,
|
|
41
|
+
code: issue.code,
|
|
42
|
+
}));
|
|
43
|
+
return {
|
|
44
|
+
message: 'Validation failed',
|
|
45
|
+
details: {
|
|
46
|
+
issues,
|
|
47
|
+
issueCount: issues.length,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Global error handler
|
|
53
|
+
*/
|
|
54
|
+
export function errorHandler(error, request, reply) {
|
|
55
|
+
// Log the error
|
|
56
|
+
logger.error('Request error', {
|
|
57
|
+
error: {
|
|
58
|
+
name: error.name,
|
|
59
|
+
message: error.message,
|
|
60
|
+
stack: error.stack,
|
|
61
|
+
},
|
|
62
|
+
requestId: request.id,
|
|
63
|
+
url: request.url,
|
|
64
|
+
method: request.method,
|
|
65
|
+
});
|
|
66
|
+
// Handle Zod validation errors
|
|
67
|
+
if (error instanceof ZodError) {
|
|
68
|
+
const { message, details } = formatZodError(error);
|
|
69
|
+
sendError(request, reply, ErrorCodes.VALIDATION_FAILED, message, 422, details);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Handle AppError
|
|
73
|
+
if (error instanceof AppError) {
|
|
74
|
+
sendError(request, reply, error.code, error.message, error.statusCode, error.details);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
// Handle Fastify errors
|
|
78
|
+
if ('statusCode' in error) {
|
|
79
|
+
const fastifyError = error;
|
|
80
|
+
// Rate limit exceeded
|
|
81
|
+
if (fastifyError.statusCode === 429) {
|
|
82
|
+
sendError(request, reply, ErrorCodes.RATE_LIMITED, 'Too many requests, please try again later', 429);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// Bad request
|
|
86
|
+
if (fastifyError.statusCode === 400) {
|
|
87
|
+
sendError(request, reply, ErrorCodes.INVALID_REQUEST, fastifyError.message || 'Bad request', 400);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Generic internal server error
|
|
92
|
+
sendError(request, reply, ErrorCodes.INTERNAL_ERROR, process.env['NODE_ENV'] === 'production'
|
|
93
|
+
? 'An internal error occurred'
|
|
94
|
+
: error.message, 500);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Not found handler
|
|
98
|
+
*/
|
|
99
|
+
export function notFoundHandler(request, reply) {
|
|
100
|
+
sendError(request, reply, 'NOT_FOUND', `Route ${request.method} ${request.url} not found`, 404);
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/middleware/error.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Skills Hub - Request Logger Middleware
|
|
3
|
+
*/
|
|
4
|
+
import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
5
|
+
import pino from 'pino';
|
|
6
|
+
export declare const logger: pino.Logger;
|
|
7
|
+
/**
|
|
8
|
+
* Request logging hook
|
|
9
|
+
*/
|
|
10
|
+
export declare function requestLogger(request: FastifyRequest, _reply: FastifyReply): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Response logging hook
|
|
13
|
+
*/
|
|
14
|
+
export declare function responseLogger(request: FastifyRequest, reply: FastifyReply, payload: unknown): Promise<unknown>;
|
|
15
|
+
/**
|
|
16
|
+
* Create a child logger with request context
|
|
17
|
+
*/
|
|
18
|
+
export declare function createRequestLogger(request: FastifyRequest): pino.Logger;
|
|
19
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/middleware/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5D,OAAO,IAAI,MAAM,MAAM,CAAC;AAGxB,eAAO,MAAM,MAAM,aAAsC,CAAC;AAE1D;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,OAAO,CAAC,CAkBlB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC,MAAM,CAMxE"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Skills Hub - Request Logger Middleware
|
|
3
|
+
*/
|
|
4
|
+
import { logger as baseLogger } from '@open-skills-hub/core';
|
|
5
|
+
// Create API-specific logger
|
|
6
|
+
export const logger = baseLogger.child({ module: 'api' });
|
|
7
|
+
/**
|
|
8
|
+
* Request logging hook
|
|
9
|
+
*/
|
|
10
|
+
export async function requestLogger(request, _reply) {
|
|
11
|
+
// Skip health check logging to reduce noise
|
|
12
|
+
if (request.url === '/health' || request.url === '/ready') {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
logger.info('Incoming request', {
|
|
16
|
+
requestId: request.id,
|
|
17
|
+
method: request.method,
|
|
18
|
+
url: request.url,
|
|
19
|
+
query: request.query,
|
|
20
|
+
userAgent: request.headers['user-agent'],
|
|
21
|
+
ip: request.ip,
|
|
22
|
+
contentLength: request.headers['content-length'],
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Response logging hook
|
|
27
|
+
*/
|
|
28
|
+
export async function responseLogger(request, reply, payload) {
|
|
29
|
+
// Skip health check logging
|
|
30
|
+
if (request.url === '/health' || request.url === '/ready') {
|
|
31
|
+
return payload;
|
|
32
|
+
}
|
|
33
|
+
const duration = request.startTime ? Date.now() - request.startTime : 0;
|
|
34
|
+
logger.info('Request completed', {
|
|
35
|
+
requestId: request.id,
|
|
36
|
+
method: request.method,
|
|
37
|
+
url: request.url,
|
|
38
|
+
statusCode: reply.statusCode,
|
|
39
|
+
duration: `${duration}ms`,
|
|
40
|
+
contentLength: reply.getHeader('content-length'),
|
|
41
|
+
});
|
|
42
|
+
return payload;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create a child logger with request context
|
|
46
|
+
*/
|
|
47
|
+
export function createRequestLogger(request) {
|
|
48
|
+
return logger.child({
|
|
49
|
+
requestId: request.id,
|
|
50
|
+
method: request.method,
|
|
51
|
+
url: request.url,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/middleware/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAG7D,6BAA6B;AAC7B,MAAM,CAAC,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;AAE1D;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAuB,EACvB,MAAoB;IAEpB,4CAA4C;IAC5C,IAAI,OAAO,CAAC,GAAG,KAAK,SAAS,IAAI,OAAO,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC1D,OAAO;IACT,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE;QAC9B,SAAS,EAAE,OAAO,CAAC,EAAE;QACrB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,SAAS,EAAE,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC;QACxC,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC;KACjD,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAAuB,EACvB,KAAmB,EACnB,OAAgB;IAEhB,4BAA4B;IAC5B,IAAI,OAAO,CAAC,GAAG,KAAK,SAAS,IAAI,OAAO,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC1D,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IAExE,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE;QAC/B,SAAS,EAAE,OAAO,CAAC,EAAE;QACrB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,QAAQ,EAAE,GAAG,QAAQ,IAAI;QACzB,aAAa,EAAE,KAAK,CAAC,SAAS,CAAC,gBAAgB,CAAC;KACjD,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAuB;IACzD,OAAO,MAAM,CAAC,KAAK,CAAC;QAClB,SAAS,EAAE,OAAO,CAAC,EAAE;QACrB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,GAAG,EAAE,OAAO,CAAC,GAAG;KACjB,CAAC,CAAC;AACL,CAAC"}
|