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
+54 -28
View File
@@ -61,6 +61,7 @@ export async function autoImportPublicHolidays(
country: { select: { code: true } },
metroCity: { select: { name: true } },
},
take: 50_000,
});
if (resources.length === 0) {
@@ -103,43 +104,68 @@ export async function autoImportPublicHolidays(
if (holidays.length === 0) continue;
const resourceIds = groupedResources.map((resource: { id: string }) => resource.id);
// Batch: collect all holiday dates, fetch existing records in one query
const holidayDates = holidays.map((h) => new Date(h.date));
const allExisting: MinimalVacation[] = await db.vacation.findMany({
where: {
resourceId: { in: resourceIds },
type: "PUBLIC_HOLIDAY",
startDate: { in: holidayDates },
endDate: { in: holidayDates },
},
select: { resourceId: true, startDate: true, endDate: true },
});
// Map: date ISO string → set of resourceIds that already have the holiday
const existingByDate = new Map<string, Set<string>>();
for (const v of allExisting) {
const key = v.startDate.toISOString();
const set = existingByDate.get(key) ?? new Set();
set.add(v.resourceId);
existingByDate.set(key, set);
}
// Build all new records for a single createMany call
const allNewRecords: Array<{
resourceId: string;
type: string;
status: string;
startDate: Date;
endDate: Date;
note: string;
isHalfDay: boolean;
approvedAt: Date;
}> = [];
const approvedAt = new Date();
for (const holiday of holidays) {
const holidayDate = new Date(holiday.date);
// Find existing records for this date + type to skip duplicates
const existing: MinimalVacation[] = await db.vacation.findMany({
where: {
resourceId: { in: resourceIds },
type: "PUBLIC_HOLIDAY",
startDate: holidayDate,
endDate: holidayDate,
},
select: { resourceId: true, startDate: true, endDate: true },
});
const existingResourceIds = new Set(existing.map((v: MinimalVacation) => v.resourceId));
const newResourceIds = resourceIds.filter((id: string) => !existingResourceIds.has(id));
const dateKey = holidayDate.toISOString();
const existingResourceIds = existingByDate.get(dateKey) ?? new Set();
totalSkipped += existingResourceIds.size;
if (newResourceIds.length === 0) continue;
const records = newResourceIds.map((resourceId: string) => ({
resourceId,
type: "PUBLIC_HOLIDAY",
status: "APPROVED",
startDate: holidayDate,
endDate: holidayDate,
note: holiday.name,
isHalfDay: false,
approvedAt: new Date(),
}));
for (const resourceId of resourceIds) {
if (existingResourceIds.has(resourceId)) continue;
allNewRecords.push({
resourceId,
type: "PUBLIC_HOLIDAY",
status: "APPROVED",
startDate: holidayDate,
endDate: holidayDate,
note: holiday.name,
isHalfDay: false,
approvedAt,
});
}
}
if (allNewRecords.length > 0) {
const result = await db.vacation.createMany({
data: records,
data: allNewRecords,
skipDuplicates: true,
});
totalCreated += result.count;
}
}
@@ -22,7 +22,9 @@ export async function resolveRecipients(
case "role": {
// Find all users with the given systemRole
const roleUsers = await db.user.findMany({
where: { systemRole: targetValue as "ADMIN" | "MANAGER" | "CONTROLLER" | "USER" | "VIEWER" },
where: {
systemRole: targetValue as "ADMIN" | "MANAGER" | "CONTROLLER" | "USER" | "VIEWER",
},
select: { id: true },
});
userIds = roleUsers.map((u) => u.id);
@@ -36,9 +38,7 @@ export async function resolveRecipients(
where: { projectId: targetValue, status: { not: "CANCELLED" } },
select: { resource: { select: { userId: true } } },
});
userIds = assignments
.map((a) => a.resource.userId)
.filter((id): id is string => !!id);
userIds = assignments.map((a) => a.resource.userId).filter((id): id is string => !!id);
break;
}
@@ -49,9 +49,7 @@ export async function resolveRecipients(
where: { orgUnitId: targetValue, isActive: true },
select: { userId: true },
});
userIds = resources
.map((r) => r.userId)
.filter((id): id is string => !!id);
userIds = resources.map((r) => r.userId).filter((id): id is string => !!id);
break;
}
@@ -59,6 +57,7 @@ export async function resolveRecipients(
// User model has no isActive — get all users
const allUsers = await db.user.findMany({
select: { id: true },
take: 10_000,
});
userIds = allUsers.map((u) => u.id);
break;