feat: implement cache layer, CSP improvements, and database performance optimizations

- Add Redis cache implementation with LRU eviction
- Enhance Content Security Policy with nonce generation
- Optimize database queries with connection pooling
- Add cache invalidation API endpoints
- Improve security monitoring performance
This commit is contained in:
2025-07-12 04:44:50 +02:00
parent 7a3eabccd9
commit e1abedb148
56 changed files with 6881 additions and 7040 deletions

View File

@ -136,8 +136,11 @@ export default function AuditLogsPage() {
});
const [selectedLog, setSelectedLog] = useState<AuditLog | null>(null);
const [hasFetched, setHasFetched] = useState(false);
const fetchAuditLogs = useCallback(async () => {
if (hasFetched) return;
try {
setLoading(true);
const params = new URLSearchParams({
@ -161,6 +164,7 @@ export default function AuditLogsPage() {
setAuditLogs(data.data.auditLogs);
setPagination(data.data.pagination);
setError(null);
setHasFetched(true);
} else {
setError(data.error || "Failed to fetch audit logs");
}
@ -170,17 +174,23 @@ export default function AuditLogsPage() {
} finally {
setLoading(false);
}
}, [pagination.page, pagination.limit, filters]);
}, [pagination.page, pagination.limit, filters, hasFetched]);
useEffect(() => {
if (session?.user?.role === "ADMIN") {
if (session?.user?.role === "ADMIN" && !hasFetched) {
fetchAuditLogs();
}
}, [session, fetchAuditLogs]);
}, [session?.user?.role, hasFetched, fetchAuditLogs]);
// Function to refresh audit logs (for filter changes)
const refreshAuditLogs = useCallback(() => {
setHasFetched(false);
}, []);
const handleFilterChange = (key: keyof typeof filters, value: string) => {
setFilters((prev) => ({ ...prev, [key]: value }));
setPagination((prev) => ({ ...prev, page: 1 })); // Reset to first page
refreshAuditLogs(); // Trigger fresh fetch with new filters
};
const clearFilters = () => {
@ -192,6 +202,7 @@ export default function AuditLogsPage() {
startDate: "",
endDate: "",
});
refreshAuditLogs(); // Trigger fresh fetch with cleared filters
};
if (session?.user?.role !== "ADMIN") {
@ -424,9 +435,10 @@ export default function AuditLogsPage() {
variant="outline"
size="sm"
disabled={!pagination.hasPrev}
onClick={() =>
setPagination((prev) => ({ ...prev, page: prev.page - 1 }))
}
onClick={() => {
setPagination((prev) => ({ ...prev, page: prev.page - 1 }));
refreshAuditLogs();
}}
>
Previous
</Button>
@ -434,9 +446,10 @@ export default function AuditLogsPage() {
variant="outline"
size="sm"
disabled={!pagination.hasNext}
onClick={() =>
setPagination((prev) => ({ ...prev, page: prev.page + 1 }))
}
onClick={() => {
setPagination((prev) => ({ ...prev, page: prev.page + 1 }));
refreshAuditLogs();
}}
>
Next
</Button>

View File

@ -503,14 +503,34 @@ function DashboardContent() {
totalSessions: overviewData.totalSessions,
avgSessionsPerDay: 0, // Will be computed properly later
avgSessionLength: null,
days: { data: [], labels: [] },
languages: { data: [], labels: [] },
categories: { data: [], labels: [] },
countries: { data: [], labels: [] },
days: {},
languages: {},
categories: {},
countries: {},
belowThresholdCount: 0,
// Map the available data
sentimentDistribution: overviewData.sentimentDistribution,
categoryDistribution: overviewData.categoryDistribution,
// Map sentiment data to individual counts
sentimentPositiveCount:
overviewData.sentimentDistribution?.find(
(s) => s.sentiment === "positive"
)?.count || 0,
sentimentNeutralCount:
overviewData.sentimentDistribution?.find(
(s) => s.sentiment === "neutral"
)?.count || 0,
sentimentNegativeCount:
overviewData.sentimentDistribution?.find(
(s) => s.sentiment === "negative"
)?.count || 0,
// Map category data to CategoryMetrics format
...(overviewData.categoryDistribution && {
categories: overviewData.categoryDistribution.reduce(
(acc, item) => {
acc[item.category] = item.count;
return acc;
},
{} as Record<string, number>
),
}),
};
setMetrics(mappedMetrics as MetricsResult);

View File

@ -49,12 +49,16 @@ interface FilterSectionProps {
setSortOrder: (_order: string) => void;
filterOptions: FilterOptions;
searchHeadingId: string;
searchId: string;
filtersHeadingId: string;
filterContentId: string;
categoryFilterId: string;
categoryHelpId: string;
languageFilterId: string;
languageHelpId: string;
startDateId: string;
endDateId: string;
sortById: string;
sortOrderId: string;
sortOrderHelpId: string;
}
@ -78,12 +82,16 @@ function FilterSection({
setSortOrder,
filterOptions,
searchHeadingId,
searchId,
filtersHeadingId,
filterContentId,
categoryFilterId,
categoryHelpId,
languageFilterId,
languageHelpId,
startDateId,
endDateId,
sortById,
sortOrderId,
sortOrderHelpId,
}: FilterSectionProps) {
@ -433,12 +441,16 @@ export default function SessionsPage() {
const [searchTerm, setSearchTerm] = useState("");
const searchHeadingId = useId();
const searchId = useId();
const filtersHeadingId = useId();
const filterContentId = useId();
const categoryFilterId = useId();
const categoryHelpId = useId();
const languageFilterId = useId();
const languageHelpId = useId();
const startDateId = useId();
const endDateId = useId();
const sortById = useId();
const sortOrderId = useId();
const sortOrderHelpId = useId();
const resultsHeadingId = useId();
@ -556,12 +568,16 @@ export default function SessionsPage() {
setSortOrder={setSortOrder}
filterOptions={filterOptions}
searchHeadingId={searchHeadingId}
searchId={searchId}
filtersHeadingId={filtersHeadingId}
filterContentId={filterContentId}
categoryFilterId={categoryFilterId}
categoryHelpId={categoryHelpId}
languageFilterId={languageFilterId}
languageHelpId={languageHelpId}
startDateId={startDateId}
endDateId={endDateId}
sortById={sortById}
sortOrderId={sortOrderId}
sortOrderHelpId={sortOrderHelpId}
/>