Files
web-app-launcher/src/routes/boards/[boardId]/+page.server.ts
T
alexei.dolgolyov 92eeeadec0 perf: batch-load app history to eliminate N+1 fetches on board load
Previously each AppWidget fetched /api/apps/{id}/history individually
on mount, causing N sequential HTTP requests. Now the board page
server load fetches all app histories in a single Prisma query via
getBatchStatusHistory() and passes them to AppWidget via Svelte
context. AppWidget uses the pre-loaded data immediately with a
fallback fetch for non-board contexts.
2026-03-25 15:36:06 +03:00

106 lines
3.3 KiB
TypeScript

import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types.js';
import * as boardService from '$lib/server/services/boardService.js';
import * as appService from '$lib/server/services/appService.js';
import * as permissionService from '$lib/server/services/permissionService.js';
import * as userService from '$lib/server/services/userService.js';
import * as groupService from '$lib/server/services/groupService.js';
import { EntityType, PermissionLevel, UserRole } from '$lib/utils/constants.js';
import { isBoardGuestAccessible } from '$lib/server/middleware/guestAccess.js';
export const load: PageServerLoad = async ({ params, locals }) => {
const { boardId } = params;
const user = locals.user;
// Permission check
if (!user) {
const isGuest = await isBoardGuestAccessible(boardId);
if (!isGuest) {
throw error(401, { message: 'Authentication required' });
}
} else if (user.role !== UserRole.ADMIN) {
const result = await permissionService.checkPermission(
EntityType.BOARD,
boardId,
user.id,
PermissionLevel.VIEW
);
if (!result.hasPermission) {
const isGuest = await isBoardGuestAccessible(boardId);
if (!isGuest) {
throw error(403, { message: 'Insufficient permissions' });
}
}
}
try {
// findBoardById includes sections -> widgets -> app -> statuses
const [board, allApps] = await Promise.all([
boardService.findBoardById(boardId),
appService.findAll()
]);
// Determine if user can edit this board
let canEdit = false;
if (user) {
if (user.role === UserRole.ADMIN) {
canEdit = true;
} else {
const editResult = await permissionService.checkPermission(
EntityType.BOARD,
boardId,
user.id,
PermissionLevel.EDIT
);
canEdit = editResult.hasPermission;
}
}
// Load users and groups for the share dialog (only if user can edit)
let users: { id: string; name: string }[] = [];
let groups: { id: string; name: string }[] = [];
if (canEdit) {
const [allUsers, allGroups] = await Promise.all([
userService.findAll(),
groupService.findAll()
]);
users = allUsers.map((u) => ({
id: u.id,
name: u.displayName || u.email
}));
groups = allGroups.map((g) => ({
id: g.id,
name: g.name
}));
}
// Batch-load sparkline history for all apps on this board (single query)
const appIdsOnBoard = board.sections
.flatMap((s: { widgets: { appId: string | null }[] }) => s.widgets)
.map((w: { appId: string | null }) => w.appId)
.filter((id: string | null): id is string => id !== null);
const historyMap = appIdsOnBoard.length > 0
? await appService.getBatchStatusHistory(appIdsOnBoard)
: new Map();
// Serialize the Map to a plain object for the client
const appHistories: Record<string, { history: { status: string; responseTime: number | null; checkedAt: string }[]; uptimePercent: number }> = {};
for (const [appId, data] of historyMap) {
appHistories[appId] = {
history: data.history.map((h) => ({ ...h, checkedAt: h.checkedAt.toISOString() })),
uptimePercent: data.uptimePercent
};
}
return { board, canEdit, allApps, users, groups, appHistories };
} catch (err) {
const message = err instanceof Error ? err.message : 'Board not found';
if (message.includes('not found')) {
throw error(404, { message: 'Board not found' });
}
throw error(500, { message });
}
};