perf(api,web,db): refactor and optimize for enterprise readiness

- Add missing @@index([userId]) on Account and Session models (auth query perf)
- Batch holiday-auto-import to eliminate N+1 query pattern (O(n) → O(1))
- Reduce SessionProvider refetchInterval from 5min to 15min
- Fix Cache-Control catch-all to stop blocking static asset caching
- Decompose assistant-tools.ts (2,562 → 809 lines) into callers, helpers, access-control modules
- Add @next/bundle-analyzer for data-driven bundle optimization
- Add @react-pdf/renderer to optimizePackageImports
- Add safety caps (take limits) on unbounded findMany queries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-11 22:34:41 +02:00
parent b3da8817dc
commit dd2c9c0f88
12 changed files with 2273 additions and 1920 deletions
@@ -0,0 +1,135 @@
import { PermissionKey, SystemRole } from "@capakraken/shared";
import type { ToolAccessRequirements, ToolContext, ToolDef } from "./shared.js";
import { AssistantVisibleError } from "./helpers.js";
export const CONTROLLER_ASSISTANT_ROLES = [
SystemRole.ADMIN,
SystemRole.MANAGER,
SystemRole.CONTROLLER,
] as const;
export const LEGACY_MONOLITHIC_TOOL_ACCESS: Partial<Record<string, ToolAccessRequirements>> = {
search_projects: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
get_project: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
update_project: { requiredPermissions: [PermissionKey.MANAGE_PROJECTS] },
create_project: { requiredPermissions: [PermissionKey.MANAGE_PROJECTS] },
delete_project: { requiredPermissions: [PermissionKey.MANAGE_PROJECTS] },
generate_project_cover: { requiredPermissions: [PermissionKey.MANAGE_PROJECTS] },
remove_project_cover: { requiredPermissions: [PermissionKey.MANAGE_PROJECTS] },
};
export type AssistantToolAccessEvaluationContext = Pick<ToolContext, "permissions" | "userRole">;
export type AssistantToolAccessFailure =
| { type: "role" }
| {
type: "permission";
permission?: PermissionKey;
message?: string;
};
function hasAssistantResourceOverviewAccess(permissions: Set<PermissionKey>): boolean {
return (
permissions.has(PermissionKey.VIEW_ALL_RESOURCES) ||
permissions.has(PermissionKey.MANAGE_RESOURCES)
);
}
export function getAssistantToolAccessRequirements(
tool: ToolDef,
): ToolAccessRequirements | undefined {
return tool.access ?? LEGACY_MONOLITHIC_TOOL_ACCESS[tool.function.name];
}
export function getAssistantToolAccessFailure(
tool: ToolDef,
ctx: AssistantToolAccessEvaluationContext,
): AssistantToolAccessFailure | null {
const access = getAssistantToolAccessRequirements(tool);
if (!access) {
return null;
}
if (
access.allowedSystemRoles &&
!access.allowedSystemRoles.includes(ctx.userRole as SystemRole)
) {
return { type: "role" };
}
const missingRequiredPermission = access.requiredPermissions?.find(
(permission) => !ctx.permissions.has(permission),
);
if (missingRequiredPermission) {
return {
type: "permission",
permission: missingRequiredPermission,
};
}
if (access.requiresPlanningRead && !ctx.permissions.has(PermissionKey.VIEW_PLANNING)) {
return {
type: "permission",
permission: PermissionKey.VIEW_PLANNING,
};
}
if (access.requiresCostView && !ctx.permissions.has(PermissionKey.VIEW_COSTS)) {
return {
type: "permission",
permission: PermissionKey.VIEW_COSTS,
};
}
if (
access.requiresAdvancedAssistant &&
!ctx.permissions.has(PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS)
) {
return {
type: "permission",
permission: PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS,
};
}
if (access.requiresResourceOverview && !hasAssistantResourceOverviewAccess(ctx.permissions)) {
return {
type: "permission",
message: "Permission denied: you need resource overview access to perform this action.",
};
}
return null;
}
export function toAssistantToolAccessError(
failure: AssistantToolAccessFailure,
): AssistantVisibleError {
if (failure.type === "role") {
return new AssistantVisibleError("You do not have permission to perform this action.");
}
if (failure.permission) {
return new AssistantVisibleError(
`Permission denied: you need the "${failure.permission}" permission to perform this action.`,
);
}
return new AssistantVisibleError(
failure.message ?? "You do not have permission to perform this action.",
);
}
export function canAccessAssistantTool(
tool: ToolDef,
ctx: AssistantToolAccessEvaluationContext,
): boolean {
return getAssistantToolAccessFailure(tool, ctx) === null;
}
export function getAvailableAssistantToolsForContext(
allTools: ToolDef[],
permissions: Set<PermissionKey>,
userRole: string,
): ToolDef[] {
return allTools.filter((tool) => canAccessAssistantTool(tool, { permissions, userRole }));
}
@@ -0,0 +1,72 @@
import { createCallerFactory } from "../../trpc.js";
import { chargeabilityReportRouter } from "../chargeability-report.js";
import { computationGraphRouter } from "../computation-graph.js";
import { timelineRouter } from "../timeline.js";
import { auditLogRouter } from "../audit-log.js";
import { importExportRouter } from "../import-export.js";
import { dispoRouter } from "../dispo.js";
import { resourceRouter } from "../resource.js";
import { settingsRouter } from "../settings.js";
import { systemRoleConfigRouter } from "../system-role-config.js";
import { userRouter } from "../user.js";
import { notificationRouter } from "../notification.js";
import { estimateRouter } from "../estimate.js";
import { webhookRouter } from "../webhook.js";
import { countryRouter } from "../country.js";
import { holidayCalendarRouter } from "../holiday-calendar.js";
import { blueprintRouter } from "../blueprint.js";
import { roleRouter } from "../role.js";
import { clientRouter } from "../client.js";
import { orgUnitRouter } from "../org-unit.js";
import { projectRouter } from "../project.js";
import { rateCardRouter } from "../rate-card.js";
import { reportRouter } from "../report.js";
import { vacationRouter } from "../vacation.js";
import { entitlementRouter } from "../entitlement.js";
import { commentRouter } from "../comment.js";
import { managementLevelRouter } from "../management-level.js";
import { utilizationCategoryRouter } from "../utilization-category.js";
import { calculationRuleRouter } from "../calculation-rules.js";
import { effortRuleRouter } from "../effort-rule.js";
import { experienceMultiplierRouter } from "../experience-multiplier.js";
import { dashboardRouter } from "../dashboard.js";
import { insightsRouter } from "../insights.js";
import { scenarioRouter } from "../scenario.js";
import { allocationRouter } from "../allocation/index.js";
import { staffingRouter } from "../staffing.js";
export const createChargeabilityReportCaller = createCallerFactory(chargeabilityReportRouter);
export const createComputationGraphCaller = createCallerFactory(computationGraphRouter);
export const createTimelineCaller = createCallerFactory(timelineRouter);
export const createAuditLogCaller = createCallerFactory(auditLogRouter);
export const createImportExportCaller = createCallerFactory(importExportRouter);
export const createDispoCaller = createCallerFactory(dispoRouter);
export const createResourceCaller = createCallerFactory(resourceRouter);
export const createSettingsCaller = createCallerFactory(settingsRouter);
export const createSystemRoleConfigCaller = createCallerFactory(systemRoleConfigRouter);
export const createUserCaller = createCallerFactory(userRouter);
export const createNotificationCaller = createCallerFactory(notificationRouter);
export const createEstimateCaller = createCallerFactory(estimateRouter);
export const createWebhookCaller = createCallerFactory(webhookRouter);
export const createCountryCaller = createCallerFactory(countryRouter);
export const createHolidayCalendarCaller = createCallerFactory(holidayCalendarRouter);
export const createBlueprintCaller = createCallerFactory(blueprintRouter);
export const createRoleCaller = createCallerFactory(roleRouter);
export const createClientCaller = createCallerFactory(clientRouter);
export const createOrgUnitCaller = createCallerFactory(orgUnitRouter);
export const createProjectCaller = createCallerFactory(projectRouter);
export const createRateCardCaller = createCallerFactory(rateCardRouter);
export const createReportCaller = createCallerFactory(reportRouter);
export const createVacationCaller = createCallerFactory(vacationRouter);
export const createEntitlementCaller = createCallerFactory(entitlementRouter);
export const createCommentCaller = createCallerFactory(commentRouter);
export const createManagementLevelCaller = createCallerFactory(managementLevelRouter);
export const createUtilizationCategoryCaller = createCallerFactory(utilizationCategoryRouter);
export const createCalculationRuleCaller = createCallerFactory(calculationRuleRouter);
export const createEffortRuleCaller = createCallerFactory(effortRuleRouter);
export const createExperienceMultiplierCaller = createCallerFactory(experienceMultiplierRouter);
export const createDashboardCaller = createCallerFactory(dashboardRouter);
export const createInsightsCaller = createCallerFactory(insightsRouter);
export const createScenarioCaller = createCallerFactory(scenarioRouter);
export const createAllocationCaller = createCallerFactory(allocationRouter);
export const createStaffingCaller = createCallerFactory(staffingRouter);
File diff suppressed because it is too large Load Diff