@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
package/dist/server.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Skills Hub - API Server Configuration
|
|
3
|
+
*/
|
|
4
|
+
import Fastify from 'fastify';
|
|
5
|
+
import cors from '@fastify/cors';
|
|
6
|
+
import helmet from '@fastify/helmet';
|
|
7
|
+
import rateLimit from '@fastify/rate-limit';
|
|
8
|
+
import multipart from '@fastify/multipart';
|
|
9
|
+
import { getConfig, getStorage, logger, } from '@open-skills-hub/core';
|
|
10
|
+
import { errorHandler, notFoundHandler } from './middleware/error.js';
|
|
11
|
+
import { requestLogger } from './middleware/logger.js';
|
|
12
|
+
import { skillsRoutes } from './routes/skills.js';
|
|
13
|
+
import { versionsRoutes } from './routes/versions.js';
|
|
14
|
+
import { searchRoutes } from './routes/search.js';
|
|
15
|
+
import { scanRoutes } from './routes/scan.js';
|
|
16
|
+
import { feedbackRoutes } from './routes/feedback.js';
|
|
17
|
+
import { auditRoutes } from './routes/audit.js';
|
|
18
|
+
import { cacheRoutes } from './routes/cache.js';
|
|
19
|
+
import { healthRoutes } from './routes/health.js';
|
|
20
|
+
/**
|
|
21
|
+
* Create and configure the Fastify server
|
|
22
|
+
*/
|
|
23
|
+
export async function createServer(options) {
|
|
24
|
+
const config = getConfig().get();
|
|
25
|
+
const serverConfig = config.server;
|
|
26
|
+
const server = Fastify({
|
|
27
|
+
logger: false, // We use our own logger
|
|
28
|
+
trustProxy: serverConfig.trustProxy,
|
|
29
|
+
requestIdHeader: 'x-request-id',
|
|
30
|
+
requestIdLogLabel: 'requestId',
|
|
31
|
+
genReqId: () => `req_${Date.now().toString(36)}_${Math.random().toString(36).substr(2, 9)}`,
|
|
32
|
+
// Increase body size limit to support large skills with many files (e.g., XSD schemas)
|
|
33
|
+
bodyLimit: 50 * 1024 * 1024, // 50MB
|
|
34
|
+
});
|
|
35
|
+
// Register plugins
|
|
36
|
+
await registerPlugins(server, config);
|
|
37
|
+
// Register middleware
|
|
38
|
+
await registerMiddleware(server);
|
|
39
|
+
// Register routes
|
|
40
|
+
await registerRoutes(server);
|
|
41
|
+
// Set error handlers
|
|
42
|
+
server.setNotFoundHandler(notFoundHandler);
|
|
43
|
+
server.setErrorHandler(errorHandler);
|
|
44
|
+
return server;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Register Fastify plugins
|
|
48
|
+
*/
|
|
49
|
+
async function registerPlugins(server, config) {
|
|
50
|
+
// CORS
|
|
51
|
+
if (config.server.cors) {
|
|
52
|
+
await server.register(cors, {
|
|
53
|
+
origin: true,
|
|
54
|
+
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
55
|
+
allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
|
|
56
|
+
exposedHeaders: ['X-Request-ID', 'X-RateLimit-Limit', 'X-RateLimit-Remaining'],
|
|
57
|
+
credentials: true,
|
|
58
|
+
maxAge: 86400,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
// Security headers
|
|
62
|
+
await server.register(helmet, {
|
|
63
|
+
contentSecurityPolicy: false, // Disabled for API
|
|
64
|
+
crossOriginResourcePolicy: { policy: 'cross-origin' },
|
|
65
|
+
});
|
|
66
|
+
// Rate limiting
|
|
67
|
+
if (config.rateLimit.enabled) {
|
|
68
|
+
await server.register(rateLimit, {
|
|
69
|
+
global: true,
|
|
70
|
+
max: config.rateLimit.max,
|
|
71
|
+
timeWindow: config.rateLimit.windowMs,
|
|
72
|
+
allowList: ['127.0.0.1'],
|
|
73
|
+
addHeadersOnExceeding: {
|
|
74
|
+
'x-ratelimit-limit': true,
|
|
75
|
+
'x-ratelimit-remaining': true,
|
|
76
|
+
'x-ratelimit-reset': true,
|
|
77
|
+
},
|
|
78
|
+
addHeaders: {
|
|
79
|
+
'x-ratelimit-limit': true,
|
|
80
|
+
'x-ratelimit-remaining': true,
|
|
81
|
+
'x-ratelimit-reset': true,
|
|
82
|
+
'retry-after': true,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// Multipart form data (for file uploads)
|
|
87
|
+
await server.register(multipart, {
|
|
88
|
+
limits: {
|
|
89
|
+
fileSize: 10 * 1024 * 1024, // 10MB max file size
|
|
90
|
+
files: 10, // Max number of files
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Register middleware
|
|
96
|
+
*/
|
|
97
|
+
async function registerMiddleware(server) {
|
|
98
|
+
// Request logging
|
|
99
|
+
server.addHook('onRequest', requestLogger);
|
|
100
|
+
// Add request timing
|
|
101
|
+
server.addHook('onRequest', async (request) => {
|
|
102
|
+
request.startTime = Date.now();
|
|
103
|
+
});
|
|
104
|
+
// Add response timing header
|
|
105
|
+
server.addHook('onSend', async (request, reply) => {
|
|
106
|
+
if (request.startTime) {
|
|
107
|
+
const duration = Date.now() - request.startTime;
|
|
108
|
+
reply.header('X-Response-Time', `${duration}ms`);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Register API routes
|
|
114
|
+
*/
|
|
115
|
+
async function registerRoutes(server) {
|
|
116
|
+
// Health check (no prefix)
|
|
117
|
+
await server.register(healthRoutes);
|
|
118
|
+
// API v1 routes
|
|
119
|
+
await server.register(async (api) => {
|
|
120
|
+
await api.register(skillsRoutes, { prefix: '/skills' });
|
|
121
|
+
await api.register(versionsRoutes, { prefix: '/skills' });
|
|
122
|
+
await api.register(searchRoutes, { prefix: '/search' });
|
|
123
|
+
await api.register(scanRoutes, { prefix: '/scan' });
|
|
124
|
+
await api.register(feedbackRoutes, { prefix: '/feedback' });
|
|
125
|
+
await api.register(auditRoutes, { prefix: '/audit' });
|
|
126
|
+
await api.register(cacheRoutes, { prefix: '/cache' });
|
|
127
|
+
}, { prefix: '/v1' });
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Start the server
|
|
131
|
+
*/
|
|
132
|
+
export async function startServer(server, options) {
|
|
133
|
+
const config = getConfig().get();
|
|
134
|
+
const host = options?.host ?? config.server.host;
|
|
135
|
+
const port = options?.port ?? config.server.port;
|
|
136
|
+
try {
|
|
137
|
+
// Initialize storage
|
|
138
|
+
const storage = await getStorage();
|
|
139
|
+
await storage.initialize();
|
|
140
|
+
logger.info('Storage initialized');
|
|
141
|
+
// Start listening
|
|
142
|
+
const address = await server.listen({ host, port });
|
|
143
|
+
logger.info(`Server started`, { address, mode: config.mode });
|
|
144
|
+
return address;
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
logger.error('Failed to start server', { error });
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Stop the server gracefully
|
|
153
|
+
*/
|
|
154
|
+
export async function stopServer(server) {
|
|
155
|
+
try {
|
|
156
|
+
await server.close();
|
|
157
|
+
const storage = await getStorage();
|
|
158
|
+
await storage.close();
|
|
159
|
+
logger.info('Server stopped gracefully');
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
logger.error('Error during server shutdown', { error });
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@open-skills-hub/api",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "REST API server for Open Skills Hub",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"skills-api": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"dev": "tsx watch src/index.ts",
|
|
14
|
+
"start": "node dist/index.js",
|
|
15
|
+
"clean": "rm -rf dist"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@open-skills-hub/core": "1.0.0",
|
|
19
|
+
"fastify": "^4.25.2",
|
|
20
|
+
"@fastify/cors": "^8.5.0",
|
|
21
|
+
"@fastify/helmet": "^11.1.1",
|
|
22
|
+
"@fastify/rate-limit": "^9.1.0",
|
|
23
|
+
"@fastify/multipart": "^8.1.0",
|
|
24
|
+
"zod": "^3.22.4",
|
|
25
|
+
"pino": "^8.17.2"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"tsx": "^4.7.0",
|
|
29
|
+
"@types/node": "^20.10.6"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/OpenSkillsHub/open-skills-hub.git",
|
|
34
|
+
"directory": "packages/api"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/OpenSkillsHub/open-skills-hub/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/OpenSkillsHub/open-skills-hub#readme",
|
|
40
|
+
"author": "Open Skills Hub Team",
|
|
41
|
+
"license": "MIT"
|
|
42
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open Skills Hub - Audit Controller
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { FastifyRequest, FastifyReply } from 'fastify';
|
|
6
|
+
import {
|
|
7
|
+
getStorage,
|
|
8
|
+
buildSkillFullName,
|
|
9
|
+
parseSkillFullName,
|
|
10
|
+
AppError,
|
|
11
|
+
ErrorCodes,
|
|
12
|
+
} from '@open-skills-hub/core';
|
|
13
|
+
import { sendSuccess } from '../middleware/error.js';
|
|
14
|
+
import type {
|
|
15
|
+
SkillNameParam,
|
|
16
|
+
IdParam,
|
|
17
|
+
AuditQuery,
|
|
18
|
+
} from '../middleware/validation.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get audit logs
|
|
22
|
+
*/
|
|
23
|
+
export async function getAuditLogs(
|
|
24
|
+
request: FastifyRequest<{ Querystring: AuditQuery }>,
|
|
25
|
+
reply: FastifyReply
|
|
26
|
+
): Promise<void> {
|
|
27
|
+
const storage = await getStorage();
|
|
28
|
+
const { eventType, actor, resourceType, resourceName, from, to, cursor, limit } = request.query;
|
|
29
|
+
|
|
30
|
+
const result = await storage.getAuditLogs({
|
|
31
|
+
eventType,
|
|
32
|
+
actor,
|
|
33
|
+
resourceType,
|
|
34
|
+
resourceName,
|
|
35
|
+
from,
|
|
36
|
+
to,
|
|
37
|
+
cursor,
|
|
38
|
+
limit,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
sendSuccess(request, reply, result.items, 200, result.pagination);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get audit logs for a specific skill
|
|
46
|
+
*/
|
|
47
|
+
export async function getSkillAuditLogs(
|
|
48
|
+
request: FastifyRequest<{ Params: SkillNameParam; Querystring: AuditQuery }>,
|
|
49
|
+
reply: FastifyReply
|
|
50
|
+
): Promise<void> {
|
|
51
|
+
const storage = await getStorage();
|
|
52
|
+
const { name } = request.params;
|
|
53
|
+
const { from, to, cursor, limit } = request.query;
|
|
54
|
+
|
|
55
|
+
// Parse name
|
|
56
|
+
const { scope, name: skillName } = parseSkillFullName(name);
|
|
57
|
+
const fullName = buildSkillFullName(skillName, scope);
|
|
58
|
+
|
|
59
|
+
const skill = await storage.getSkillByName(fullName);
|
|
60
|
+
if (!skill) {
|
|
61
|
+
throw new AppError(
|
|
62
|
+
ErrorCodes.SKILL_NOT_FOUND,
|
|
63
|
+
`Skill '${fullName}' not found`,
|
|
64
|
+
404
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const result = await storage.getResourceAuditLogs('skill', skill.id, {
|
|
69
|
+
cursor,
|
|
70
|
+
limit,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Filter by date if provided
|
|
74
|
+
let items = result.items;
|
|
75
|
+
if (from || to) {
|
|
76
|
+
items = items.filter(log => {
|
|
77
|
+
const timestamp = new Date(log.timestamp).getTime();
|
|
78
|
+
if (from && timestamp < new Date(from).getTime()) return false;
|
|
79
|
+
if (to && timestamp > new Date(to).getTime()) return false;
|
|
80
|
+
return true;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
sendSuccess(request, reply, items, 200, result.pagination);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get audit log by ID
|
|
89
|
+
*/
|
|
90
|
+
export async function getAuditLogById(
|
|
91
|
+
request: FastifyRequest<{ Params: IdParam }>,
|
|
92
|
+
reply: FastifyReply
|
|
93
|
+
): Promise<void> {
|
|
94
|
+
const storage = await getStorage();
|
|
95
|
+
const { id } = request.params;
|
|
96
|
+
|
|
97
|
+
// Search for the log
|
|
98
|
+
const result = await storage.getAuditLogs({ limit: 1000 });
|
|
99
|
+
const log = result.items.find(l => l.id === id);
|
|
100
|
+
|
|
101
|
+
if (!log) {
|
|
102
|
+
throw new AppError(
|
|
103
|
+
ErrorCodes.SKILL_NOT_FOUND, // Using generic not found
|
|
104
|
+
`Audit log '${id}' not found`,
|
|
105
|
+
404
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
sendSuccess(request, reply, log);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get audit statistics
|
|
114
|
+
*/
|
|
115
|
+
export async function getAuditStats(
|
|
116
|
+
request: FastifyRequest<{ Querystring: { days?: number } }>,
|
|
117
|
+
reply: FastifyReply
|
|
118
|
+
): Promise<void> {
|
|
119
|
+
const storage = await getStorage();
|
|
120
|
+
const days = request.query.days ?? 30;
|
|
121
|
+
|
|
122
|
+
const fromDate = new Date();
|
|
123
|
+
fromDate.setDate(fromDate.getDate() - days);
|
|
124
|
+
|
|
125
|
+
const result = await storage.getAuditLogs({
|
|
126
|
+
from: fromDate.toISOString(),
|
|
127
|
+
limit: 10000,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const logs = result.items;
|
|
131
|
+
|
|
132
|
+
// Calculate stats
|
|
133
|
+
const byEventType: Record<string, number> = {};
|
|
134
|
+
const byActorType: Record<string, number> = {};
|
|
135
|
+
const byResult: Record<string, number> = {};
|
|
136
|
+
const byDay: Record<string, number> = {};
|
|
137
|
+
|
|
138
|
+
for (const log of logs) {
|
|
139
|
+
// By event type
|
|
140
|
+
byEventType[log.eventType] = (byEventType[log.eventType] || 0) + 1;
|
|
141
|
+
|
|
142
|
+
// By actor type
|
|
143
|
+
byActorType[log.actor.type] = (byActorType[log.actor.type] || 0) + 1;
|
|
144
|
+
|
|
145
|
+
// By result
|
|
146
|
+
byResult[log.result] = (byResult[log.result] || 0) + 1;
|
|
147
|
+
|
|
148
|
+
// By day
|
|
149
|
+
const day = log.timestamp.split('T')[0];
|
|
150
|
+
if (day) {
|
|
151
|
+
byDay[day] = (byDay[day] || 0) + 1;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Convert byDay to sorted array
|
|
156
|
+
const dailyActivity = Object.entries(byDay)
|
|
157
|
+
.map(([date, count]) => ({ date, count }))
|
|
158
|
+
.sort((a, b) => a.date.localeCompare(b.date));
|
|
159
|
+
|
|
160
|
+
sendSuccess(request, reply, {
|
|
161
|
+
total: logs.length,
|
|
162
|
+
period: {
|
|
163
|
+
from: fromDate.toISOString(),
|
|
164
|
+
to: new Date().toISOString(),
|
|
165
|
+
days,
|
|
166
|
+
},
|
|
167
|
+
byEventType,
|
|
168
|
+
byActorType,
|
|
169
|
+
byResult,
|
|
170
|
+
dailyActivity,
|
|
171
|
+
successRate: logs.length > 0
|
|
172
|
+
? (byResult['success'] || 0) / logs.length
|
|
173
|
+
: 0,
|
|
174
|
+
});
|
|
175
|
+
}
|