fix: cover proxy routing, session filtering, library tab deep-link, profile UX
Some checks failed
Release / Test backend (push) Successful in 1m3s
Release / Test UI (push) Successful in 58s
Release / Build and push images (push) Successful in 5m55s
Release / Deploy to prod (push) Failing after 48s
Release / Deploy to homelab (push) Successful in 21s
Release / Gitea Release (push) Successful in 29s
Some checks failed
Release / Test backend (push) Successful in 1m3s
Release / Test UI (push) Successful in 58s
Release / Build and push images (push) Successful in 5m55s
Release / Deploy to prod (push) Failing after 48s
Release / Deploy to homelab (push) Successful in 21s
Release / Gitea Release (push) Successful in 29s
- Catalogue/cover: rewrite raw scraped cover URLs to /api/cover/{domain}/{slug}
in handleCatalogue so all covers route through the backend proxy; fix broken
cdn.novelfire.net fallback in handleGetCover to read stored URL from PocketBase
- Catalogue/profile: add Svelte 5 onerror handlers on cover <img> tags to show
letter-initial placeholder when image fails to load
- Library page: read ?status URL param to initialise activeShelf tab on load so
/books?status=reading correctly pre-selects the Reading tab
- Sessions: filter bot/tool user-agents (curl, python, wget, etc.) and debug-IP
sessions from listUserSessions display; also purge them in pruneStaleUserSessions
- Profile: show email under username, quick stats chips (streak/chapters/completed)
in header, reading count on Library row, dedicated Sign out row, history covers
routed through /api/cover proxy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1431,10 +1431,34 @@ export async function isSessionRevoked(authSessionId: string): Promise<boolean>
|
||||
}
|
||||
|
||||
/**
|
||||
* List all active sessions for a user.
|
||||
* Returns true for user-agents that are clearly automated tools (curl, scrapers,
|
||||
* debug logins, etc.) that should not appear in the user-facing sessions list.
|
||||
* These sessions still exist in the DB so auth checks continue to work.
|
||||
*/
|
||||
function isBotUserAgent(ua: string): boolean {
|
||||
if (!ua) return false;
|
||||
const lower = ua.toLowerCase();
|
||||
return (
|
||||
lower.startsWith('curl/') ||
|
||||
lower.startsWith('python') ||
|
||||
lower.startsWith('wget/') ||
|
||||
lower.startsWith('go-http-client') ||
|
||||
lower.startsWith('axios/') ||
|
||||
lower.startsWith('node-fetch') ||
|
||||
lower.startsWith('undici') ||
|
||||
lower.startsWith('okhttp') ||
|
||||
lower.startsWith('java/')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all active sessions for a user, excluding non-browser/tool sessions
|
||||
* (curl, debug-login artifacts, scrapers, etc.) from the displayed list.
|
||||
* The records still exist in the DB so auth validity checks are unaffected.
|
||||
*/
|
||||
export async function listUserSessions(userId: string): Promise<UserSession[]> {
|
||||
return listAll<UserSession>('user_sessions', `user_id="${userId}"`, '-last_seen');
|
||||
const all = await listAll<UserSession>('user_sessions', `user_id="${userId}"`, '-last_seen');
|
||||
return all.filter((s) => !isBotUserAgent(s.user_agent) && s.ip !== 'debug');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1453,9 +1477,11 @@ async function pruneStaleUserSessions(
|
||||
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
||||
const toDelete = new Set<string>();
|
||||
|
||||
// Mark stale sessions
|
||||
// Mark stale sessions and debug/tool sessions for deletion
|
||||
for (const s of all) {
|
||||
if (s.last_seen < cutoff) toDelete.add(s.id);
|
||||
if (s.last_seen < cutoff || s.ip === 'debug' || isBotUserAgent(s.user_agent)) {
|
||||
toDelete.add(s.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Mark excess sessions beyond the cap (oldest first — list is sorted -last_seen)
|
||||
|
||||
Reference in New Issue
Block a user