Analytics: unique visitors this week + probed-URLs list; Scripture Index: most-studied strip + print; admin warning for unparseable passage references; v2.44.0
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6a5d525d6e
commit
e9d9dd0d50
13 files changed
+143
-9
No files matched your search
@@ -172,6 +172,7 @@ export const EMPTY_HIT_STATS = {
|
||||
byPath: {},
|
||||
byPathReal: {},
|
||||
byPathBot: {},
|
||||
byPathUnknown: {},
|
||||
byDay: {},
|
||||
byDayReal: {},
|
||||
byDayBot: {},
|
||||
|
||||
@@ -167,6 +167,7 @@ export function loadHitStatsFromDisk() {
|
||||
byPath: parsed?.byPath && typeof parsed.byPath === 'object' ? parsed.byPath : {},
|
||||
byPathReal: parsed?.byPathReal && typeof parsed.byPathReal === 'object' ? parsed.byPathReal : {},
|
||||
byPathBot: parsed?.byPathBot && typeof parsed.byPathBot === 'object' ? parsed.byPathBot : {},
|
||||
byPathUnknown: parsed?.byPathUnknown && typeof parsed.byPathUnknown === 'object' ? parsed.byPathUnknown : {},
|
||||
byDay: parsed?.byDay && typeof parsed.byDay === 'object' ? parsed.byDay : {},
|
||||
byDayReal: parsed?.byDayReal && typeof parsed.byDayReal === 'object' ? parsed.byDayReal : {},
|
||||
byDayBot: parsed?.byDayBot && typeof parsed.byDayBot === 'object' ? parsed.byDayBot : {},
|
||||
@@ -1250,6 +1251,7 @@ function sanitizeLoadedHitStats(value) {
|
||||
byPath: value?.byPath && typeof value.byPath === 'object' ? value.byPath : {},
|
||||
byPathReal: value?.byPathReal && typeof value.byPathReal === 'object' ? value.byPathReal : {},
|
||||
byPathBot: value?.byPathBot && typeof value.byPathBot === 'object' ? value.byPathBot : {},
|
||||
byPathUnknown: value?.byPathUnknown && typeof value.byPathUnknown === 'object' ? value.byPathUnknown : {},
|
||||
byDay: value?.byDay && typeof value.byDay === 'object' ? value.byDay : {},
|
||||
byDayReal: value?.byDayReal && typeof value.byDayReal === 'object' ? value.byDayReal : {},
|
||||
byDayBot: value?.byDayBot && typeof value.byDayBot === 'object' ? value.byDayBot : {},
|
||||
|
||||
@@ -393,6 +393,13 @@ export function register(app) {
|
||||
firstHitAt: state.hitStats.firstHitAt,
|
||||
lastHitAt: state.hitStats.lastHitAt,
|
||||
topPaths, topPathsReal, topPathsBot,
|
||||
topUnknownPaths: Object.entries(state.hitStats.byPathUnknown ?? {})
|
||||
.sort((a, b) => b[1] - a[1]).slice(0, 25).map(([path, hits]) => ({ path, hits })),
|
||||
uniqueVisitors7d: new Set(
|
||||
state.visitorStats.recentVisits
|
||||
.filter(v => Date.now() - new Date(v.at).getTime() <= 7 * 24 * 60 * 60 * 1000)
|
||||
.map(v => v.visitorId),
|
||||
).size,
|
||||
last7Days, last7DaysReal, last7DaysBot,
|
||||
last30DaysTotal, last30DaysRealTotal, last30DaysBotTotal,
|
||||
botReasons,
|
||||
|
||||
@@ -484,6 +484,8 @@ const KNOWN_SITE_SEGMENTS = new Set([
|
||||
'privacy', 'terms', 'salvation', 'qr',
|
||||
])
|
||||
|
||||
const MAX_UNKNOWN_PATHS = 300
|
||||
|
||||
export function isKnownSitePath(pathname) {
|
||||
const first = String(pathname ?? '/').split('/')[1] ?? ''
|
||||
return first === '' || KNOWN_SITE_SEGMENTS.has(first)
|
||||
@@ -593,6 +595,21 @@ export function recordHit(pathname, isBot = false, botReason = null) {
|
||||
state.hitStats.lastHitAt = nowIso
|
||||
state.hitStats.firstHitAt = state.hitStats.firstHitAt ?? nowIso
|
||||
|
||||
if (isBot && botReason === 'unknown-path') {
|
||||
// Probe/scanner URLs: keep a bounded list for the admin view instead of one stats key per URL
|
||||
const unknown = (state.hitStats.byPathUnknown ??= {})
|
||||
const key = safePath.slice(0, 120)
|
||||
if (key in unknown || Object.keys(unknown).length < MAX_UNKNOWN_PATHS) unknown[key] = (unknown[key] ?? 0) + 1
|
||||
else unknown['(other)'] = (unknown['(other)'] ?? 0) + 1
|
||||
state.hitStats.botHits += 1
|
||||
state.hitStats.byPathBot['(unknown)'] = (state.hitStats.byPathBot['(unknown)'] ?? 0) + 1
|
||||
state.hitStats.byDayBot[dayKey] = (state.hitStats.byDayBot[dayKey] ?? 0) + 1
|
||||
state.hitStats.botReasons[botReason] = (state.hitStats.botReasons[botReason] ?? 0) + 1
|
||||
state.hitStats.byPath['(unknown)'] = (state.hitStats.byPath['(unknown)'] ?? 0) + 1
|
||||
state.hitStats.byDay[dayKey] = (state.hitStats.byDay[dayKey] ?? 0) + 1
|
||||
return
|
||||
}
|
||||
|
||||
if (isBot) {
|
||||
state.hitStats.botHits += 1
|
||||
state.hitStats.byPathBot[safePath] = (state.hitStats.byPathBot[safePath] ?? 0) + 1
|
||||
|
||||
Reference in new issue
Block a user