express-sequelize-traffic 0.1.0 → 0.2.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/README.md +10 -1
- package/dist/index.cjs +880 -0
- package/dist/index.js +844 -0
- package/package.json +13 -6
- package/examples/express-app.js +0 -106
- package/src/dashboard.js +0 -132
- package/src/index.js +0 -67
- package/src/middleware.js +0 -111
- package/src/models/TrafficLog.js +0 -83
- package/src/realtime.js +0 -71
- package/src/routes/analyticsRoutes.js +0 -83
- package/src/services/analyticsService.js +0 -286
- package/src/utils/dashboardAuth.js +0 -71
- package/src/utils/routeMatcher.js +0 -53
- package/src/utils/safeAsync.js +0 -13
- /package/{src → dist}/dashboard-public/assets/index-CaWHQ-tp.js +0 -0
- /package/{src → dist}/dashboard-public/assets/index-iT93XJlh.css +0 -0
- /package/{src → dist}/dashboard-public/index.html +0 -0
package/package.json
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-sequelize-traffic",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Privacy-first, self-hosted Express request analytics with Sequelize storage and an optional realtime dashboard.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
7
8
|
"workspaces": [
|
|
8
9
|
"dashboard-app"
|
|
9
10
|
],
|
|
10
11
|
"exports": {
|
|
11
|
-
".":
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
}
|
|
12
17
|
},
|
|
13
18
|
"files": [
|
|
14
|
-
"
|
|
15
|
-
"examples",
|
|
19
|
+
"dist",
|
|
16
20
|
"README.md",
|
|
17
21
|
"LICENSE"
|
|
18
22
|
],
|
|
19
23
|
"scripts": {
|
|
20
24
|
"dev": "npm run example",
|
|
21
|
-
"build": "npm run build:dashboard",
|
|
25
|
+
"build": "npm run build:package && npm run build:dashboard",
|
|
26
|
+
"build:package": "node scripts/build-package.mjs",
|
|
22
27
|
"build:dashboard": "npm run build --workspace dashboard-app",
|
|
28
|
+
"preexample": "npm run build",
|
|
23
29
|
"example": "node examples/express-app.js",
|
|
24
30
|
"prepack": "npm run build"
|
|
25
31
|
},
|
|
@@ -42,6 +48,7 @@
|
|
|
42
48
|
"socket.io": "^4.8.1"
|
|
43
49
|
},
|
|
44
50
|
"devDependencies": {
|
|
51
|
+
"esbuild": "^0.25.4",
|
|
45
52
|
"express": "^4.21.2",
|
|
46
53
|
"sequelize": "^6.37.5",
|
|
47
54
|
"sqlite3": "^5.1.7"
|
package/examples/express-app.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import express from "express";
|
|
2
|
-
import http from "node:http";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { Sequelize } from "sequelize";
|
|
6
|
-
import { createTrafficTracker } from "../src/index.js";
|
|
7
|
-
|
|
8
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
-
const __dirname = path.dirname(__filename);
|
|
10
|
-
|
|
11
|
-
const app = express();
|
|
12
|
-
const server = http.createServer(app);
|
|
13
|
-
|
|
14
|
-
const sequelize = new Sequelize({
|
|
15
|
-
dialect: "sqlite",
|
|
16
|
-
storage: path.join(__dirname, "traffic-demo.sqlite"),
|
|
17
|
-
logging: false,
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
const traffic = createTrafficTracker({
|
|
21
|
-
sequelize,
|
|
22
|
-
getUserId: (req) => req.user?.id || req.headers["x-user-id"] || null,
|
|
23
|
-
getSessionId: (req) => req.headers["x-session-id"] || null,
|
|
24
|
-
slowRouteThresholdMs: 700,
|
|
25
|
-
trackIp: false,
|
|
26
|
-
trackUserAgent: true,
|
|
27
|
-
ignoredRoutes: ["/health", "/favicon.ico"],
|
|
28
|
-
dashboard: {
|
|
29
|
-
enabled: true,
|
|
30
|
-
mountPath: "/traffic-dashboard",
|
|
31
|
-
username: process.env.TRAFFIC_ADMIN_USER || "admin",
|
|
32
|
-
password: process.env.TRAFFIC_ADMIN_PASS || "password",
|
|
33
|
-
},
|
|
34
|
-
debug: true,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
function wait(ms) {
|
|
38
|
-
return new Promise((resolve) => {
|
|
39
|
-
setTimeout(resolve, ms);
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
app.use(express.json());
|
|
44
|
-
|
|
45
|
-
app.use((req, _res, next) => {
|
|
46
|
-
const userId = req.headers["x-user-id"];
|
|
47
|
-
|
|
48
|
-
req.user = userId ? { id: String(userId) } : null;
|
|
49
|
-
next();
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
app.use(traffic.middleware);
|
|
53
|
-
traffic.attachRealtime(server);
|
|
54
|
-
app.use("/traffic-dashboard", traffic.dashboard);
|
|
55
|
-
|
|
56
|
-
app.get("/health", (_req, res) => {
|
|
57
|
-
res.json({ ok: true });
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
app.get("/api/products", async (_req, res) => {
|
|
61
|
-
await wait(120);
|
|
62
|
-
|
|
63
|
-
res.json([
|
|
64
|
-
{ id: 1, name: "Signal Desk" },
|
|
65
|
-
{ id: 2, name: "Route Pulse" },
|
|
66
|
-
{ id: 3, name: "Latency Watch" },
|
|
67
|
-
]);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
app.get("/api/slow", async (_req, res) => {
|
|
71
|
-
await wait(1200);
|
|
72
|
-
res.json({ ok: true, message: "This route should show up as slow." });
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
app.get("/api/error", async (_req, res) => {
|
|
76
|
-
await wait(320);
|
|
77
|
-
res.status(500).json({ error: "Synthetic example failure." });
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
app.get("/api/users/:id", async (req, res) => {
|
|
81
|
-
await wait(180);
|
|
82
|
-
res.json({
|
|
83
|
-
id: req.params.id,
|
|
84
|
-
name: `User ${req.params.id}`,
|
|
85
|
-
requestedAt: new Date().toISOString(),
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
async function start() {
|
|
90
|
-
await sequelize.authenticate();
|
|
91
|
-
await traffic.sync();
|
|
92
|
-
|
|
93
|
-
const port = Number(process.env.PORT || 3000);
|
|
94
|
-
|
|
95
|
-
server.listen(port, () => {
|
|
96
|
-
console.log(`Example app running on http://localhost:${port}`);
|
|
97
|
-
console.log(
|
|
98
|
-
`Dashboard available on http://localhost:${port}/traffic-dashboard/`,
|
|
99
|
-
);
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
start().catch((error) => {
|
|
104
|
-
console.error("Failed to start example app.", error);
|
|
105
|
-
process.exitCode = 1;
|
|
106
|
-
});
|
package/src/dashboard.js
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import express from "express";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { createAnalyticsRouter } from "./routes/analyticsRoutes.js";
|
|
6
|
-
import { createBasicAuthMiddleware } from "./utils/dashboardAuth.js";
|
|
7
|
-
|
|
8
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
-
const __dirname = path.dirname(__filename);
|
|
10
|
-
const dashboardBuildDirectory = path.resolve(__dirname, "dashboard-public");
|
|
11
|
-
const dashboardIndexFile = path.join(dashboardBuildDirectory, "index.html");
|
|
12
|
-
|
|
13
|
-
function createMissingBuildPage() {
|
|
14
|
-
return `<!doctype html>
|
|
15
|
-
<html lang="en">
|
|
16
|
-
<head>
|
|
17
|
-
<meta charset="utf-8" />
|
|
18
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
19
|
-
<title>Traffic Dashboard Build Missing</title>
|
|
20
|
-
<style>
|
|
21
|
-
body {
|
|
22
|
-
margin: 0;
|
|
23
|
-
font-family: "Segoe UI", sans-serif;
|
|
24
|
-
background: linear-gradient(135deg, #0f172a, #1e293b);
|
|
25
|
-
color: #e2e8f0;
|
|
26
|
-
min-height: 100vh;
|
|
27
|
-
display: grid;
|
|
28
|
-
place-items: center;
|
|
29
|
-
padding: 24px;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.panel {
|
|
33
|
-
max-width: 640px;
|
|
34
|
-
background: rgba(15, 23, 42, 0.9);
|
|
35
|
-
border: 1px solid rgba(148, 163, 184, 0.2);
|
|
36
|
-
border-radius: 24px;
|
|
37
|
-
padding: 32px;
|
|
38
|
-
box-shadow: 0 24px 60px rgba(15, 23, 42, 0.45);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
h1 {
|
|
42
|
-
margin-top: 0;
|
|
43
|
-
font-size: 2rem;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
code {
|
|
47
|
-
background: rgba(148, 163, 184, 0.16);
|
|
48
|
-
padding: 2px 6px;
|
|
49
|
-
border-radius: 6px;
|
|
50
|
-
}
|
|
51
|
-
</style>
|
|
52
|
-
</head>
|
|
53
|
-
<body>
|
|
54
|
-
<div class="panel">
|
|
55
|
-
<h1>Dashboard build not found</h1>
|
|
56
|
-
<p>
|
|
57
|
-
The analytics APIs are available, but the Vite dashboard has not been built
|
|
58
|
-
into <code>src/dashboard-public</code> yet.
|
|
59
|
-
</p>
|
|
60
|
-
<p>Run <code>npm run build:dashboard</code> in this package to generate the static dashboard assets.</p>
|
|
61
|
-
</div>
|
|
62
|
-
</body>
|
|
63
|
-
</html>`;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function createDisabledDashboardRouter() {
|
|
67
|
-
const router = express.Router();
|
|
68
|
-
|
|
69
|
-
router.use("/api", (_req, res) => {
|
|
70
|
-
res.status(404).json({ error: "Dashboard is disabled." });
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
router.use((_req, res) => {
|
|
74
|
-
res
|
|
75
|
-
.status(404)
|
|
76
|
-
.send("The traffic dashboard is disabled for this tracker instance.");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
return router;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function createDashboardRouter({
|
|
83
|
-
analyticsService,
|
|
84
|
-
dashboard = {},
|
|
85
|
-
debug = false,
|
|
86
|
-
}) {
|
|
87
|
-
const router = express.Router();
|
|
88
|
-
|
|
89
|
-
router.use(createBasicAuthMiddleware(dashboard));
|
|
90
|
-
router.use("/api", createAnalyticsRouter({ analyticsService, debug }));
|
|
91
|
-
|
|
92
|
-
const hasBuiltDashboard = fs.existsSync(dashboardIndexFile);
|
|
93
|
-
|
|
94
|
-
if (hasBuiltDashboard) {
|
|
95
|
-
router.get("/", (req, res, next) => {
|
|
96
|
-
if (!req.originalUrl.endsWith("/")) {
|
|
97
|
-
res.redirect(302, `${req.baseUrl}/`);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
next();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
router.use(
|
|
105
|
-
express.static(dashboardBuildDirectory, {
|
|
106
|
-
index: false,
|
|
107
|
-
}),
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
router.get("*", (req, res, next) => {
|
|
111
|
-
if (req.path.startsWith("/api")) {
|
|
112
|
-
next();
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
res.sendFile(dashboardIndexFile);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
return router;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
router.get("*", (req, res, next) => {
|
|
123
|
-
if (req.path.startsWith("/api")) {
|
|
124
|
-
next();
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
res.status(503).send(createMissingBuildPage());
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
return router;
|
|
132
|
-
}
|
package/src/index.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { defineTrafficLogModel } from "./models/TrafficLog.js";
|
|
2
|
-
import { createTrackingMiddleware } from "./middleware.js";
|
|
3
|
-
import { createAnalyticsService } from "./services/analyticsService.js";
|
|
4
|
-
import {
|
|
5
|
-
createDashboardRouter,
|
|
6
|
-
createDisabledDashboardRouter,
|
|
7
|
-
} from "./dashboard.js";
|
|
8
|
-
import { createRealtimeBridge } from "./realtime.js";
|
|
9
|
-
|
|
10
|
-
export function createTrafficTracker(options = {}) {
|
|
11
|
-
const {
|
|
12
|
-
sequelize,
|
|
13
|
-
getUserId,
|
|
14
|
-
getSessionId,
|
|
15
|
-
slowRouteThresholdMs = 1000,
|
|
16
|
-
ignoredRoutes = [],
|
|
17
|
-
trackIp = false,
|
|
18
|
-
trackUserAgent = true,
|
|
19
|
-
dashboard = {},
|
|
20
|
-
debug = false,
|
|
21
|
-
} = options;
|
|
22
|
-
|
|
23
|
-
if (!sequelize) {
|
|
24
|
-
throw new Error(
|
|
25
|
-
"createTrafficTracker requires a Sequelize instance via options.sequelize.",
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const TrafficLog = defineTrafficLogModel(sequelize);
|
|
30
|
-
const analyticsService = createAnalyticsService(TrafficLog);
|
|
31
|
-
const realtimeBridge = createRealtimeBridge({ dashboard, debug });
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
middleware: createTrackingMiddleware({
|
|
35
|
-
TrafficLog,
|
|
36
|
-
getUserId,
|
|
37
|
-
getSessionId,
|
|
38
|
-
slowRouteThresholdMs,
|
|
39
|
-
ignoredRoutes,
|
|
40
|
-
trackIp,
|
|
41
|
-
trackUserAgent,
|
|
42
|
-
debug,
|
|
43
|
-
realtimeBridge,
|
|
44
|
-
}),
|
|
45
|
-
dashboard: dashboard.enabled
|
|
46
|
-
? createDashboardRouter({
|
|
47
|
-
analyticsService,
|
|
48
|
-
dashboard,
|
|
49
|
-
debug,
|
|
50
|
-
})
|
|
51
|
-
: createDisabledDashboardRouter(),
|
|
52
|
-
attachRealtime(server) {
|
|
53
|
-
return realtimeBridge.attachRealtime(server);
|
|
54
|
-
},
|
|
55
|
-
async sync(syncOptions = {}) {
|
|
56
|
-
return TrafficLog.sync(syncOptions);
|
|
57
|
-
},
|
|
58
|
-
getModel() {
|
|
59
|
-
return TrafficLog;
|
|
60
|
-
},
|
|
61
|
-
getSocketPath() {
|
|
62
|
-
return realtimeBridge.getSocketPath();
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export { defineTrafficLogModel };
|
package/src/middleware.js
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { safeAsync } from "./utils/safeAsync.js";
|
|
2
|
-
import { resolveTrackedRoute, shouldIgnoreRoute } from "./utils/routeMatcher.js";
|
|
3
|
-
|
|
4
|
-
function resolveOptionalValue(getter, req, debug, label) {
|
|
5
|
-
if (typeof getter !== "function") {
|
|
6
|
-
return null;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
try {
|
|
10
|
-
return getter(req) ?? null;
|
|
11
|
-
} catch (error) {
|
|
12
|
-
if (debug) {
|
|
13
|
-
console.error(
|
|
14
|
-
`[express-sequelize-traffic] Failed to resolve ${label}.`,
|
|
15
|
-
error,
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function createLogPayload({
|
|
24
|
-
req,
|
|
25
|
-
res,
|
|
26
|
-
startedAt,
|
|
27
|
-
endedAt,
|
|
28
|
-
slowRouteThresholdMs,
|
|
29
|
-
trackIp,
|
|
30
|
-
trackUserAgent,
|
|
31
|
-
getUserId,
|
|
32
|
-
getSessionId,
|
|
33
|
-
debug,
|
|
34
|
-
}) {
|
|
35
|
-
const route = resolveTrackedRoute(req);
|
|
36
|
-
const durationMs = Math.max(0, endedAt.getTime() - startedAt.getTime());
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
userId: resolveOptionalValue(getUserId, req, debug, "userId"),
|
|
40
|
-
sessionId: resolveOptionalValue(getSessionId, req, debug, "sessionId"),
|
|
41
|
-
method: req.method,
|
|
42
|
-
route,
|
|
43
|
-
originalUrl: req.originalUrl || route,
|
|
44
|
-
statusCode: res.statusCode,
|
|
45
|
-
durationMs,
|
|
46
|
-
isSlow: durationMs >= slowRouteThresholdMs,
|
|
47
|
-
ip: trackIp ? req.ip || req.socket?.remoteAddress || null : null,
|
|
48
|
-
userAgent: trackUserAgent ? req.get("user-agent") || null : null,
|
|
49
|
-
startedAt,
|
|
50
|
-
endedAt,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function createTrackingMiddleware({
|
|
55
|
-
TrafficLog,
|
|
56
|
-
getUserId,
|
|
57
|
-
getSessionId,
|
|
58
|
-
slowRouteThresholdMs = 1000,
|
|
59
|
-
ignoredRoutes = [],
|
|
60
|
-
trackIp = false,
|
|
61
|
-
trackUserAgent = true,
|
|
62
|
-
debug = false,
|
|
63
|
-
realtimeBridge,
|
|
64
|
-
}) {
|
|
65
|
-
return (req, res, next) => {
|
|
66
|
-
const startedAt = new Date();
|
|
67
|
-
|
|
68
|
-
res.once("finish", () => {
|
|
69
|
-
const endedAt = new Date();
|
|
70
|
-
const route = resolveTrackedRoute(req);
|
|
71
|
-
const originalUrl = req.originalUrl || route;
|
|
72
|
-
|
|
73
|
-
if (shouldIgnoreRoute({ route, originalUrl, ignoredRoutes })) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
void safeAsync(
|
|
78
|
-
async () => {
|
|
79
|
-
const payload = createLogPayload({
|
|
80
|
-
req,
|
|
81
|
-
res,
|
|
82
|
-
startedAt,
|
|
83
|
-
endedAt,
|
|
84
|
-
slowRouteThresholdMs,
|
|
85
|
-
trackIp,
|
|
86
|
-
trackUserAgent,
|
|
87
|
-
getUserId,
|
|
88
|
-
getSessionId,
|
|
89
|
-
debug,
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
const createdLog = await TrafficLog.create(payload);
|
|
93
|
-
|
|
94
|
-
realtimeBridge?.emitNewRequest(createdLog.get({ plain: true }));
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
onError: (error) => {
|
|
98
|
-
if (debug) {
|
|
99
|
-
console.error(
|
|
100
|
-
"[express-sequelize-traffic] Failed to persist traffic log.",
|
|
101
|
-
error,
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
next();
|
|
110
|
-
};
|
|
111
|
-
}
|
package/src/models/TrafficLog.js
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { DataTypes } from "sequelize";
|
|
2
|
-
|
|
3
|
-
export function defineTrafficLogModel(sequelize) {
|
|
4
|
-
if (!sequelize) {
|
|
5
|
-
throw new Error("A Sequelize instance is required to define TrafficLog.");
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
if (sequelize.models.TrafficLog) {
|
|
9
|
-
return sequelize.models.TrafficLog;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
return sequelize.define(
|
|
13
|
-
"TrafficLog",
|
|
14
|
-
{
|
|
15
|
-
id: {
|
|
16
|
-
type: DataTypes.BIGINT,
|
|
17
|
-
autoIncrement: true,
|
|
18
|
-
primaryKey: true,
|
|
19
|
-
},
|
|
20
|
-
userId: {
|
|
21
|
-
type: DataTypes.STRING,
|
|
22
|
-
allowNull: true,
|
|
23
|
-
},
|
|
24
|
-
sessionId: {
|
|
25
|
-
type: DataTypes.STRING,
|
|
26
|
-
allowNull: true,
|
|
27
|
-
},
|
|
28
|
-
method: {
|
|
29
|
-
type: DataTypes.STRING(16),
|
|
30
|
-
allowNull: false,
|
|
31
|
-
},
|
|
32
|
-
route: {
|
|
33
|
-
type: DataTypes.STRING,
|
|
34
|
-
allowNull: false,
|
|
35
|
-
},
|
|
36
|
-
originalUrl: {
|
|
37
|
-
type: DataTypes.STRING,
|
|
38
|
-
allowNull: false,
|
|
39
|
-
},
|
|
40
|
-
statusCode: {
|
|
41
|
-
type: DataTypes.INTEGER,
|
|
42
|
-
allowNull: false,
|
|
43
|
-
},
|
|
44
|
-
durationMs: {
|
|
45
|
-
type: DataTypes.INTEGER,
|
|
46
|
-
allowNull: false,
|
|
47
|
-
},
|
|
48
|
-
isSlow: {
|
|
49
|
-
type: DataTypes.BOOLEAN,
|
|
50
|
-
allowNull: false,
|
|
51
|
-
defaultValue: false,
|
|
52
|
-
},
|
|
53
|
-
ip: {
|
|
54
|
-
type: DataTypes.STRING,
|
|
55
|
-
allowNull: true,
|
|
56
|
-
},
|
|
57
|
-
userAgent: {
|
|
58
|
-
type: DataTypes.TEXT,
|
|
59
|
-
allowNull: true,
|
|
60
|
-
},
|
|
61
|
-
startedAt: {
|
|
62
|
-
type: DataTypes.DATE,
|
|
63
|
-
allowNull: false,
|
|
64
|
-
},
|
|
65
|
-
endedAt: {
|
|
66
|
-
type: DataTypes.DATE,
|
|
67
|
-
allowNull: false,
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
tableName: "traffic_logs",
|
|
72
|
-
indexes: [
|
|
73
|
-
{ fields: ["userId"] },
|
|
74
|
-
{ fields: ["sessionId"] },
|
|
75
|
-
{ fields: ["route"] },
|
|
76
|
-
{ fields: ["method"] },
|
|
77
|
-
{ fields: ["statusCode"] },
|
|
78
|
-
{ fields: ["durationMs"] },
|
|
79
|
-
{ fields: ["createdAt"] },
|
|
80
|
-
],
|
|
81
|
-
},
|
|
82
|
-
);
|
|
83
|
-
}
|
package/src/realtime.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { Server } from "socket.io";
|
|
2
|
-
import { createSocketAuthMiddleware } from "./utils/dashboardAuth.js";
|
|
3
|
-
|
|
4
|
-
function normalizeSocketPath(dashboard = {}) {
|
|
5
|
-
const mountPath = dashboard.mountPath || "/traffic-dashboard";
|
|
6
|
-
const sanitizedMountPath = mountPath.startsWith("/")
|
|
7
|
-
? mountPath
|
|
8
|
-
: `/${mountPath}`;
|
|
9
|
-
|
|
10
|
-
return `${sanitizedMountPath.replace(/\/$/, "")}/socket.io`;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function buildRealtimePayload(log) {
|
|
14
|
-
return {
|
|
15
|
-
userId: log.userId,
|
|
16
|
-
sessionId: log.sessionId,
|
|
17
|
-
method: log.method,
|
|
18
|
-
route: log.route,
|
|
19
|
-
originalUrl: log.originalUrl,
|
|
20
|
-
statusCode: Number(log.statusCode),
|
|
21
|
-
durationMs: Number(log.durationMs),
|
|
22
|
-
isSlow: Boolean(log.isSlow),
|
|
23
|
-
createdAt: log.createdAt,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function createRealtimeBridge({ dashboard = {}, debug = false } = {}) {
|
|
28
|
-
let io = null;
|
|
29
|
-
const socketPath = normalizeSocketPath(dashboard);
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
attachRealtime(server) {
|
|
33
|
-
if (!server) {
|
|
34
|
-
throw new Error("An HTTP server instance is required for realtime.");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (io) {
|
|
38
|
-
return io;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
io = new Server(server, {
|
|
42
|
-
path: socketPath,
|
|
43
|
-
serveClient: false,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
io.use(createSocketAuthMiddleware(dashboard));
|
|
47
|
-
|
|
48
|
-
if (debug) {
|
|
49
|
-
io.on("connection", (socket) => {
|
|
50
|
-
console.info(
|
|
51
|
-
`[express-sequelize-traffic] Dashboard realtime connected: ${socket.id}`,
|
|
52
|
-
);
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return io;
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
emitNewRequest(log) {
|
|
60
|
-
if (!io) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
io.emit("traffic:new-request", buildRealtimePayload(log));
|
|
65
|
-
},
|
|
66
|
-
|
|
67
|
-
getSocketPath() {
|
|
68
|
-
return socketPath;
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import express from "express";
|
|
2
|
-
import { safeAsync } from "../utils/safeAsync.js";
|
|
3
|
-
|
|
4
|
-
function sendApiError(res, message) {
|
|
5
|
-
res.status(500).json({ error: message });
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function createAnalyticsRouter({ analyticsService, debug = false }) {
|
|
9
|
-
const router = express.Router();
|
|
10
|
-
|
|
11
|
-
const logApiError = (scope) => (error) => {
|
|
12
|
-
if (debug) {
|
|
13
|
-
console.error(`[express-sequelize-traffic] ${scope}`, error);
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
router.get("/overview", async (_req, res) => {
|
|
18
|
-
const data = await safeAsync(() => analyticsService.getOverview(), {
|
|
19
|
-
onError: logApiError("Failed to load overview analytics."),
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
if (!data) {
|
|
23
|
-
sendApiError(res, "Unable to load overview analytics.");
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
res.json(data);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
router.get("/live", async (_req, res) => {
|
|
31
|
-
const data = await safeAsync(() => analyticsService.getLive(), {
|
|
32
|
-
onError: logApiError("Failed to load live traffic logs."),
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
if (!data) {
|
|
36
|
-
sendApiError(res, "Unable to load live traffic logs.");
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
res.json(data);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
router.get("/routes", async (_req, res) => {
|
|
44
|
-
const data = await safeAsync(() => analyticsService.getRoutes(), {
|
|
45
|
-
onError: logApiError("Failed to load route analytics."),
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
if (!data) {
|
|
49
|
-
sendApiError(res, "Unable to load route analytics.");
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
res.json(data);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
router.get("/users", async (_req, res) => {
|
|
57
|
-
const data = await safeAsync(() => analyticsService.getUsers(), {
|
|
58
|
-
onError: logApiError("Failed to load user analytics."),
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
if (!data) {
|
|
62
|
-
sendApiError(res, "Unable to load user analytics.");
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
res.json(data);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
router.get("/errors", async (_req, res) => {
|
|
70
|
-
const data = await safeAsync(() => analyticsService.getErrors(), {
|
|
71
|
-
onError: logApiError("Failed to load error analytics."),
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
if (!data) {
|
|
75
|
-
sendApiError(res, "Unable to load error analytics.");
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
res.json(data);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
return router;
|
|
83
|
-
}
|