lapeh 1.0.1 → 1.0.3
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/.env.example +16 -16
- package/bin/index.js +105 -105
- package/framework.md +105 -0
- package/nodemon.json +6 -0
- package/package.json +73 -70
- package/prisma/base.prisma +8 -8
- package/prisma/migrations/20251225163737_init/migration.sql +236 -236
- package/prisma/migrations/migration_lock.toml +3 -3
- package/prisma/schema.prisma +0 -7
- package/prisma.config.ts +15 -15
- package/readme.md +126 -120
- package/scripts/generate-jwt-secret.js +38 -38
- package/scripts/init-project.js +71 -0
- package/scripts/make-model.js +42 -42
- package/scripts/make-module.js +158 -158
- package/src/controllers/rbacController.ts +353 -353
- package/src/middleware/auth.ts +56 -56
- package/src/middleware/error.ts +7 -7
- package/src/middleware/visitor.ts +180 -180
- package/src/models/schema.prisma +159 -159
- package/src/routes/rbac.ts +55 -55
- package/src/schema/auth-schema.ts +62 -62
- package/src/utils/pagination.ts +56 -56
package/src/middleware/auth.ts
CHANGED
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from "express";
|
|
2
|
-
import jwt from "jsonwebtoken";
|
|
3
|
-
import { sendError } from "../utils/response";
|
|
4
|
-
import { ACCESS_TOKEN_EXPIRES_IN_SECONDS } from "../controllers/authController";
|
|
5
|
-
|
|
6
|
-
export function requireAuth(req: Request, res: Response, next: NextFunction) {
|
|
7
|
-
const header = req.headers.authorization;
|
|
8
|
-
if (!header || !header.startsWith("Bearer ")) {
|
|
9
|
-
sendError(res, 401, "Unauthorized");
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
const token = header.slice(7);
|
|
13
|
-
const secret = process.env.JWT_SECRET;
|
|
14
|
-
if (!secret) {
|
|
15
|
-
sendError(res, 500, "Server misconfigured");
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
try {
|
|
19
|
-
const payload = jwt.verify(token, secret) as {
|
|
20
|
-
userId: string;
|
|
21
|
-
role: string;
|
|
22
|
-
};
|
|
23
|
-
(req as any).user = { userId: payload.userId, role: payload.role };
|
|
24
|
-
|
|
25
|
-
const accessExpiresInSeconds = ACCESS_TOKEN_EXPIRES_IN_SECONDS;
|
|
26
|
-
const accessExpiresAt = new Date(
|
|
27
|
-
Date.now() + accessExpiresInSeconds * 1000
|
|
28
|
-
).toISOString();
|
|
29
|
-
const newToken = jwt.sign(
|
|
30
|
-
{ userId: payload.userId, role: payload.role },
|
|
31
|
-
secret,
|
|
32
|
-
{ expiresIn: accessExpiresInSeconds }
|
|
33
|
-
);
|
|
34
|
-
res.setHeader("x-access-token", newToken);
|
|
35
|
-
res.setHeader("x-access-expires-at", accessExpiresAt);
|
|
36
|
-
|
|
37
|
-
next();
|
|
38
|
-
} catch {
|
|
39
|
-
sendError(res, 401, "Invalid token");
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function requireAdmin(req: Request, res: Response, next: NextFunction) {
|
|
44
|
-
const user = (req as any).user as
|
|
45
|
-
| { userId: string; role: string }
|
|
46
|
-
| undefined;
|
|
47
|
-
if (!user) {
|
|
48
|
-
sendError(res, 401, "Unauthorized");
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
if (user.role !== "admin" && user.role !== "super_admin") {
|
|
52
|
-
sendError(res, 403, "Forbidden");
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
next();
|
|
56
|
-
}
|
|
1
|
+
import { Request, Response, NextFunction } from "express";
|
|
2
|
+
import jwt from "jsonwebtoken";
|
|
3
|
+
import { sendError } from "../utils/response";
|
|
4
|
+
import { ACCESS_TOKEN_EXPIRES_IN_SECONDS } from "../controllers/authController";
|
|
5
|
+
|
|
6
|
+
export function requireAuth(req: Request, res: Response, next: NextFunction) {
|
|
7
|
+
const header = req.headers.authorization;
|
|
8
|
+
if (!header || !header.startsWith("Bearer ")) {
|
|
9
|
+
sendError(res, 401, "Unauthorized");
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const token = header.slice(7);
|
|
13
|
+
const secret = process.env.JWT_SECRET;
|
|
14
|
+
if (!secret) {
|
|
15
|
+
sendError(res, 500, "Server misconfigured");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const payload = jwt.verify(token, secret) as {
|
|
20
|
+
userId: string;
|
|
21
|
+
role: string;
|
|
22
|
+
};
|
|
23
|
+
(req as any).user = { userId: payload.userId, role: payload.role };
|
|
24
|
+
|
|
25
|
+
const accessExpiresInSeconds = ACCESS_TOKEN_EXPIRES_IN_SECONDS;
|
|
26
|
+
const accessExpiresAt = new Date(
|
|
27
|
+
Date.now() + accessExpiresInSeconds * 1000
|
|
28
|
+
).toISOString();
|
|
29
|
+
const newToken = jwt.sign(
|
|
30
|
+
{ userId: payload.userId, role: payload.role },
|
|
31
|
+
secret,
|
|
32
|
+
{ expiresIn: accessExpiresInSeconds }
|
|
33
|
+
);
|
|
34
|
+
res.setHeader("x-access-token", newToken);
|
|
35
|
+
res.setHeader("x-access-expires-at", accessExpiresAt);
|
|
36
|
+
|
|
37
|
+
next();
|
|
38
|
+
} catch {
|
|
39
|
+
sendError(res, 401, "Invalid token");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function requireAdmin(req: Request, res: Response, next: NextFunction) {
|
|
44
|
+
const user = (req as any).user as
|
|
45
|
+
| { userId: string; role: string }
|
|
46
|
+
| undefined;
|
|
47
|
+
if (!user) {
|
|
48
|
+
sendError(res, 401, "Unauthorized");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (user.role !== "admin" && user.role !== "super_admin") {
|
|
52
|
+
sendError(res, 403, "Forbidden");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
next();
|
|
56
|
+
}
|
package/src/middleware/error.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from "express"
|
|
2
|
-
import { sendError } from "../utils/response"
|
|
3
|
-
export function errorHandler(err: any, _req: Request, res: Response, _next: NextFunction) {
|
|
4
|
-
const code = err.statusCode || 500
|
|
5
|
-
const msg = err.message || "Internal Server Error"
|
|
6
|
-
sendError(res, code, msg)
|
|
7
|
-
}
|
|
1
|
+
import { Request, Response, NextFunction } from "express"
|
|
2
|
+
import { sendError } from "../utils/response"
|
|
3
|
+
export function errorHandler(err: any, _req: Request, res: Response, _next: NextFunction) {
|
|
4
|
+
const code = err.statusCode || 500
|
|
5
|
+
const msg = err.message || "Internal Server Error"
|
|
6
|
+
sendError(res, code, msg)
|
|
7
|
+
}
|
|
8
8
|
|
|
@@ -1,180 +1,180 @@
|
|
|
1
|
-
import { Request, Response, NextFunction } from "express";
|
|
2
|
-
import { v4 as uuidv4 } from "uuid";
|
|
3
|
-
import { redis, useRedis } from "../redis";
|
|
4
|
-
|
|
5
|
-
type DayMemoryStats = {
|
|
6
|
-
requests: number;
|
|
7
|
-
newVisitors: number;
|
|
8
|
-
visitors: Set<string>;
|
|
9
|
-
newVisitorsMobile: number;
|
|
10
|
-
visitorsMobile: Set<string>;
|
|
11
|
-
ipAddresses: number;
|
|
12
|
-
ipSet: Set<string>;
|
|
13
|
-
sessions: number;
|
|
14
|
-
sessionSet: Set<string>;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const memoryStats = new Map<string, DayMemoryStats>();
|
|
18
|
-
const globalVisitors = new Set<string>();
|
|
19
|
-
|
|
20
|
-
function formatDateKey(d: Date) {
|
|
21
|
-
const dd = String(d.getDate()).padStart(2, "0");
|
|
22
|
-
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
23
|
-
const yyyy = d.getFullYear();
|
|
24
|
-
return `${dd}-${mm}-${yyyy}`;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function parseCookies(header: string | undefined) {
|
|
28
|
-
const cookies: Record<string, string> = {};
|
|
29
|
-
if (!header) return cookies;
|
|
30
|
-
const parts = header.split(";");
|
|
31
|
-
for (const part of parts) {
|
|
32
|
-
const [k, v] = part.split("=").map((s) => s.trim());
|
|
33
|
-
if (k && v) cookies[k] = decodeURIComponent(v);
|
|
34
|
-
}
|
|
35
|
-
return cookies;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function isMobileUserAgent(ua: string | undefined) {
|
|
39
|
-
if (!ua) return false;
|
|
40
|
-
return /Mobile|Android|iPhone|iPad|iPod/i.test(ua);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export async function visitorCounter(
|
|
44
|
-
req: Request,
|
|
45
|
-
res: Response,
|
|
46
|
-
next: NextFunction
|
|
47
|
-
) {
|
|
48
|
-
const now = new Date();
|
|
49
|
-
const dateKey = formatDateKey(now);
|
|
50
|
-
const ip =
|
|
51
|
-
req.ip ||
|
|
52
|
-
(req.headers["x-forwarded-for"] as string | undefined) ||
|
|
53
|
-
req.socket.remoteAddress ||
|
|
54
|
-
"";
|
|
55
|
-
const userAgent = req.headers["user-agent"] as string | undefined;
|
|
56
|
-
const mobile = isMobileUserAgent(userAgent);
|
|
57
|
-
|
|
58
|
-
const cookies = parseCookies(req.headers.cookie);
|
|
59
|
-
let visitorId = cookies["visitor_id"];
|
|
60
|
-
if (!visitorId) {
|
|
61
|
-
visitorId = uuidv4();
|
|
62
|
-
res.cookie("visitor_id", visitorId, {
|
|
63
|
-
httpOnly: true,
|
|
64
|
-
sameSite: "lax",
|
|
65
|
-
maxAge: 365 * 24 * 60 * 60 * 1000,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
let sessionId = cookies["visitor_session_id"];
|
|
70
|
-
if (!sessionId) {
|
|
71
|
-
sessionId = uuidv4();
|
|
72
|
-
res.cookie("visitor_session_id", sessionId, {
|
|
73
|
-
httpOnly: true,
|
|
74
|
-
sameSite: "lax",
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (useRedis && redis) {
|
|
79
|
-
const base = dateKey;
|
|
80
|
-
const kRequests = `requests-${base}`;
|
|
81
|
-
const kNewVisitors = `new-visitors-${base}`;
|
|
82
|
-
const kVisitors = `visitors-${base}`;
|
|
83
|
-
const kNewVisitorsMobile = `new-visitors-from-mobile-${base}`;
|
|
84
|
-
const kVisitorsMobile = `visitors-from-mobile-${base}`;
|
|
85
|
-
const kIpAddresses = `ip-addresses-${base}`;
|
|
86
|
-
const kSessions = `sessions-${base}`;
|
|
87
|
-
const kVisitorsSet = `visitors-set-${base}`;
|
|
88
|
-
const kVisitorsMobileSet = `visitors-from-mobile-set-${base}`;
|
|
89
|
-
const kIpSet = `ip-addresses-set-${base}`;
|
|
90
|
-
const kSessionsSet = `sessions-set-${base}`;
|
|
91
|
-
const kVisitorsAll = `visitors-all`;
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
await redis.incr(kRequests);
|
|
95
|
-
|
|
96
|
-
const isNewEver = await redis.sadd(kVisitorsAll, visitorId);
|
|
97
|
-
if (isNewEver === 1) {
|
|
98
|
-
await redis.incr(kNewVisitors);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const addedVisitor = await redis.sadd(kVisitorsSet, visitorId);
|
|
102
|
-
if (addedVisitor === 1) {
|
|
103
|
-
await redis.incr(kVisitors);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (mobile) {
|
|
107
|
-
const addedMobileVisitor = await redis.sadd(
|
|
108
|
-
kVisitorsMobileSet,
|
|
109
|
-
visitorId
|
|
110
|
-
);
|
|
111
|
-
if (addedMobileVisitor === 1) {
|
|
112
|
-
await redis.incr(kVisitorsMobile);
|
|
113
|
-
}
|
|
114
|
-
if (isNewEver === 1) {
|
|
115
|
-
await redis.incr(kNewVisitorsMobile);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (ip) {
|
|
120
|
-
const addedIp = await redis.sadd(kIpSet, ip);
|
|
121
|
-
if (addedIp === 1) {
|
|
122
|
-
await redis.incr(kIpAddresses);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const addedSession = await redis.sadd(kSessionsSet, sessionId);
|
|
127
|
-
if (addedSession === 1) {
|
|
128
|
-
await redis.incr(kSessions);
|
|
129
|
-
}
|
|
130
|
-
} catch {
|
|
131
|
-
}
|
|
132
|
-
} else {
|
|
133
|
-
let stats = memoryStats.get(dateKey);
|
|
134
|
-
if (!stats) {
|
|
135
|
-
stats = {
|
|
136
|
-
requests: 0,
|
|
137
|
-
newVisitors: 0,
|
|
138
|
-
visitors: new Set<string>(),
|
|
139
|
-
newVisitorsMobile: 0,
|
|
140
|
-
visitorsMobile: new Set<string>(),
|
|
141
|
-
ipAddresses: 0,
|
|
142
|
-
ipSet: new Set<string>(),
|
|
143
|
-
sessions: 0,
|
|
144
|
-
sessionSet: new Set<string>(),
|
|
145
|
-
};
|
|
146
|
-
memoryStats.set(dateKey, stats);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
stats.requests += 1;
|
|
150
|
-
|
|
151
|
-
if (!globalVisitors.has(visitorId)) {
|
|
152
|
-
globalVisitors.add(visitorId);
|
|
153
|
-
stats.newVisitors += 1;
|
|
154
|
-
if (mobile) {
|
|
155
|
-
stats.newVisitorsMobile += 1;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (!stats.visitors.has(visitorId)) {
|
|
160
|
-
stats.visitors.add(visitorId);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (mobile && !stats.visitorsMobile.has(visitorId)) {
|
|
164
|
-
stats.visitorsMobile.add(visitorId);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (ip && !stats.ipSet.has(ip)) {
|
|
168
|
-
stats.ipSet.add(ip);
|
|
169
|
-
stats.ipAddresses += 1;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (!stats.sessionSet.has(sessionId)) {
|
|
173
|
-
stats.sessionSet.add(sessionId);
|
|
174
|
-
stats.sessions += 1;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
next();
|
|
179
|
-
}
|
|
180
|
-
|
|
1
|
+
import { Request, Response, NextFunction } from "express";
|
|
2
|
+
import { v4 as uuidv4 } from "uuid";
|
|
3
|
+
import { redis, useRedis } from "../redis";
|
|
4
|
+
|
|
5
|
+
type DayMemoryStats = {
|
|
6
|
+
requests: number;
|
|
7
|
+
newVisitors: number;
|
|
8
|
+
visitors: Set<string>;
|
|
9
|
+
newVisitorsMobile: number;
|
|
10
|
+
visitorsMobile: Set<string>;
|
|
11
|
+
ipAddresses: number;
|
|
12
|
+
ipSet: Set<string>;
|
|
13
|
+
sessions: number;
|
|
14
|
+
sessionSet: Set<string>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const memoryStats = new Map<string, DayMemoryStats>();
|
|
18
|
+
const globalVisitors = new Set<string>();
|
|
19
|
+
|
|
20
|
+
function formatDateKey(d: Date) {
|
|
21
|
+
const dd = String(d.getDate()).padStart(2, "0");
|
|
22
|
+
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
23
|
+
const yyyy = d.getFullYear();
|
|
24
|
+
return `${dd}-${mm}-${yyyy}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseCookies(header: string | undefined) {
|
|
28
|
+
const cookies: Record<string, string> = {};
|
|
29
|
+
if (!header) return cookies;
|
|
30
|
+
const parts = header.split(";");
|
|
31
|
+
for (const part of parts) {
|
|
32
|
+
const [k, v] = part.split("=").map((s) => s.trim());
|
|
33
|
+
if (k && v) cookies[k] = decodeURIComponent(v);
|
|
34
|
+
}
|
|
35
|
+
return cookies;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isMobileUserAgent(ua: string | undefined) {
|
|
39
|
+
if (!ua) return false;
|
|
40
|
+
return /Mobile|Android|iPhone|iPad|iPod/i.test(ua);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function visitorCounter(
|
|
44
|
+
req: Request,
|
|
45
|
+
res: Response,
|
|
46
|
+
next: NextFunction
|
|
47
|
+
) {
|
|
48
|
+
const now = new Date();
|
|
49
|
+
const dateKey = formatDateKey(now);
|
|
50
|
+
const ip =
|
|
51
|
+
req.ip ||
|
|
52
|
+
(req.headers["x-forwarded-for"] as string | undefined) ||
|
|
53
|
+
req.socket.remoteAddress ||
|
|
54
|
+
"";
|
|
55
|
+
const userAgent = req.headers["user-agent"] as string | undefined;
|
|
56
|
+
const mobile = isMobileUserAgent(userAgent);
|
|
57
|
+
|
|
58
|
+
const cookies = parseCookies(req.headers.cookie);
|
|
59
|
+
let visitorId = cookies["visitor_id"];
|
|
60
|
+
if (!visitorId) {
|
|
61
|
+
visitorId = uuidv4();
|
|
62
|
+
res.cookie("visitor_id", visitorId, {
|
|
63
|
+
httpOnly: true,
|
|
64
|
+
sameSite: "lax",
|
|
65
|
+
maxAge: 365 * 24 * 60 * 60 * 1000,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let sessionId = cookies["visitor_session_id"];
|
|
70
|
+
if (!sessionId) {
|
|
71
|
+
sessionId = uuidv4();
|
|
72
|
+
res.cookie("visitor_session_id", sessionId, {
|
|
73
|
+
httpOnly: true,
|
|
74
|
+
sameSite: "lax",
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (useRedis && redis) {
|
|
79
|
+
const base = dateKey;
|
|
80
|
+
const kRequests = `requests-${base}`;
|
|
81
|
+
const kNewVisitors = `new-visitors-${base}`;
|
|
82
|
+
const kVisitors = `visitors-${base}`;
|
|
83
|
+
const kNewVisitorsMobile = `new-visitors-from-mobile-${base}`;
|
|
84
|
+
const kVisitorsMobile = `visitors-from-mobile-${base}`;
|
|
85
|
+
const kIpAddresses = `ip-addresses-${base}`;
|
|
86
|
+
const kSessions = `sessions-${base}`;
|
|
87
|
+
const kVisitorsSet = `visitors-set-${base}`;
|
|
88
|
+
const kVisitorsMobileSet = `visitors-from-mobile-set-${base}`;
|
|
89
|
+
const kIpSet = `ip-addresses-set-${base}`;
|
|
90
|
+
const kSessionsSet = `sessions-set-${base}`;
|
|
91
|
+
const kVisitorsAll = `visitors-all`;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
await redis.incr(kRequests);
|
|
95
|
+
|
|
96
|
+
const isNewEver = await redis.sadd(kVisitorsAll, visitorId);
|
|
97
|
+
if (isNewEver === 1) {
|
|
98
|
+
await redis.incr(kNewVisitors);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const addedVisitor = await redis.sadd(kVisitorsSet, visitorId);
|
|
102
|
+
if (addedVisitor === 1) {
|
|
103
|
+
await redis.incr(kVisitors);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (mobile) {
|
|
107
|
+
const addedMobileVisitor = await redis.sadd(
|
|
108
|
+
kVisitorsMobileSet,
|
|
109
|
+
visitorId
|
|
110
|
+
);
|
|
111
|
+
if (addedMobileVisitor === 1) {
|
|
112
|
+
await redis.incr(kVisitorsMobile);
|
|
113
|
+
}
|
|
114
|
+
if (isNewEver === 1) {
|
|
115
|
+
await redis.incr(kNewVisitorsMobile);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (ip) {
|
|
120
|
+
const addedIp = await redis.sadd(kIpSet, ip);
|
|
121
|
+
if (addedIp === 1) {
|
|
122
|
+
await redis.incr(kIpAddresses);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const addedSession = await redis.sadd(kSessionsSet, sessionId);
|
|
127
|
+
if (addedSession === 1) {
|
|
128
|
+
await redis.incr(kSessions);
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
let stats = memoryStats.get(dateKey);
|
|
134
|
+
if (!stats) {
|
|
135
|
+
stats = {
|
|
136
|
+
requests: 0,
|
|
137
|
+
newVisitors: 0,
|
|
138
|
+
visitors: new Set<string>(),
|
|
139
|
+
newVisitorsMobile: 0,
|
|
140
|
+
visitorsMobile: new Set<string>(),
|
|
141
|
+
ipAddresses: 0,
|
|
142
|
+
ipSet: new Set<string>(),
|
|
143
|
+
sessions: 0,
|
|
144
|
+
sessionSet: new Set<string>(),
|
|
145
|
+
};
|
|
146
|
+
memoryStats.set(dateKey, stats);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
stats.requests += 1;
|
|
150
|
+
|
|
151
|
+
if (!globalVisitors.has(visitorId)) {
|
|
152
|
+
globalVisitors.add(visitorId);
|
|
153
|
+
stats.newVisitors += 1;
|
|
154
|
+
if (mobile) {
|
|
155
|
+
stats.newVisitorsMobile += 1;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!stats.visitors.has(visitorId)) {
|
|
160
|
+
stats.visitors.add(visitorId);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (mobile && !stats.visitorsMobile.has(visitorId)) {
|
|
164
|
+
stats.visitorsMobile.add(visitorId);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (ip && !stats.ipSet.has(ip)) {
|
|
168
|
+
stats.ipSet.add(ip);
|
|
169
|
+
stats.ipAddresses += 1;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!stats.sessionSet.has(sessionId)) {
|
|
173
|
+
stats.sessionSet.add(sessionId);
|
|
174
|
+
stats.sessions += 1;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
next();
|
|
179
|
+
}
|
|
180
|
+
|