Remove the TTS voice section from the profile page — it fetched /api/voices on every mount, blocking paint for the full round-trip. Voice selection lives on the chapter page where voices are already loaded. Rewrite the server load to run avatar, sessions+stats, and reading history all concurrently via Promise.allSettled instead of sequentially, cutting SSR latency by ~2-3x on the profile route.
91 lines
3.0 KiB
TypeScript
91 lines
3.0 KiB
TypeScript
import { redirect } from '@sveltejs/kit';
|
|
import type { PageServerLoad } from './$types';
|
|
import {
|
|
listUserSessions,
|
|
getUserByUsername,
|
|
getUserStats,
|
|
allProgress,
|
|
getBooksBySlugs
|
|
} from '$lib/server/pocketbase';
|
|
import { resolveAvatarUrl } from '$lib/server/minio';
|
|
import { log } from '$lib/server/logger';
|
|
|
|
export const load: PageServerLoad = async ({ locals }) => {
|
|
if (!locals.user) {
|
|
redirect(302, '/login');
|
|
}
|
|
|
|
// Helper: fetch reading history (progress → books, sequential by necessity)
|
|
async function fetchHistory() {
|
|
const progress = await allProgress(locals.sessionId, locals.user!.id);
|
|
const recent = progress.slice(0, 50);
|
|
const books = await getBooksBySlugs(new Set(recent.map((p) => p.slug)));
|
|
const bookMap = new Map(books.map((b) => [b.slug, b]));
|
|
return recent.map((p) => ({
|
|
slug: p.slug,
|
|
chapter: p.chapter,
|
|
updated: p.updated,
|
|
title: bookMap.get(p.slug)?.title ?? p.slug,
|
|
cover: bookMap.get(p.slug)?.cover ?? null
|
|
}));
|
|
}
|
|
|
|
// Helper: fetch avatar/email/polarCustomerId (getUserByUsername → resolveAvatarUrl)
|
|
async function fetchUserRecord() {
|
|
const record = await getUserByUsername(locals.user!.username);
|
|
const avatarUrl = await resolveAvatarUrl(locals.user!.id, record?.avatar_url);
|
|
return {
|
|
avatarUrl,
|
|
email: record?.email ?? null,
|
|
polarCustomerId: record?.polar_customer_id ?? null
|
|
};
|
|
}
|
|
|
|
// Run all three independent groups concurrently
|
|
const [userRecord, sessionsResult, statsResult, historyResult] = await Promise.allSettled([
|
|
fetchUserRecord(),
|
|
listUserSessions(locals.user.id),
|
|
getUserStats(locals.sessionId, locals.user.id),
|
|
fetchHistory()
|
|
]);
|
|
|
|
if (userRecord.status === 'rejected')
|
|
log.warn('profile', 'avatar fetch failed (non-fatal)', { err: String(userRecord.reason) });
|
|
if (sessionsResult.status === 'rejected')
|
|
log.warn('profile', 'sessions fetch failed (non-fatal)', { err: String(sessionsResult.reason) });
|
|
if (statsResult.status === 'rejected')
|
|
log.warn('profile', 'stats fetch failed (non-fatal)', { err: String(statsResult.reason) });
|
|
if (historyResult.status === 'rejected')
|
|
log.warn('profile', 'history fetch failed (non-fatal)', { err: String(historyResult.reason) });
|
|
|
|
const { avatarUrl = null, email = null, polarCustomerId = null } =
|
|
userRecord.status === 'fulfilled' ? userRecord.value : {};
|
|
const sessions =
|
|
sessionsResult.status === 'fulfilled' ? sessionsResult.value : [];
|
|
const stats =
|
|
statsResult.status === 'fulfilled' ? statsResult.value : null;
|
|
const history =
|
|
historyResult.status === 'fulfilled' ? historyResult.value : [];
|
|
|
|
return {
|
|
user: locals.user,
|
|
avatarUrl,
|
|
email,
|
|
polarCustomerId,
|
|
stats: stats ?? {
|
|
totalChaptersRead: 0, booksReading: 0, booksCompleted: 0,
|
|
booksPlanToRead: 0, booksDropped: 0, topGenres: [],
|
|
avgRatingGiven: 0, streak: 0
|
|
},
|
|
sessions: sessions.map((s) => ({
|
|
id: s.id,
|
|
user_agent: s.user_agent,
|
|
ip: s.ip,
|
|
created_at: s.created_at,
|
|
last_seen: s.last_seen,
|
|
is_current: s.session_id === locals.user!.authSessionId
|
|
})),
|
|
history
|
|
};
|
|
};
|