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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user