optimize admin users table sorting and pagination by pushing supported sorts to Prisma

This commit is contained in:
Yusuf İpek
2026-02-24 17:23:44 +03:00
parent 68c63ce020
commit 6a6b48f6a9
+99 -31
View File
@@ -1,4 +1,5 @@
import { Metadata } from 'next'; import { Metadata } from 'next';
import { Prisma } from '@prisma/client';
import { db } from '@/lib/db'; import { db } from '@/lib/db';
import { auth } from '@/lib/auth'; import { auth } from '@/lib/auth';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
@@ -74,6 +75,53 @@ function getSortIndicator(column: SortBy, activeSortBy: SortBy, activeSortDirect
return activeSortDirection === 'asc' ? '↑' : '↓'; return activeSortDirection === 'asc' ? '↑' : '↓';
} }
function canSortInDb(sortBy: SortBy): boolean {
return sortBy === 'user'
|| sortBy === 'joinedDate'
|| sortBy === 'workspacesOwned'
|| sortBy === 'projectsOwned'
|| sortBy === 'totalComments';
}
function getUsersOrderBy(sortBy: SortBy, sortDirection: SortDirection): Prisma.UserOrderByWithRelationInput[] {
const createdAtTieBreaker: Prisma.UserOrderByWithRelationInput = { createdAt: 'desc' };
if (sortBy === 'user') {
return [
{ name: sortDirection },
{ email: sortDirection },
createdAtTieBreaker,
];
}
if (sortBy === 'joinedDate') {
return [{ createdAt: sortDirection }];
}
if (sortBy === 'workspacesOwned') {
return [
{ ownedWorkspaces: { _count: sortDirection } },
createdAtTieBreaker,
];
}
if (sortBy === 'projectsOwned') {
return [
{ projects: { _count: sortDirection } },
createdAtTieBreaker,
];
}
if (sortBy === 'totalComments') {
return [
{ comments: { _count: sortDirection } },
createdAtTieBreaker,
];
}
return [createdAtTieBreaker];
}
export default async function AdminUsersPage({ export default async function AdminUsersPage({
searchParams searchParams
}: { }: {
@@ -86,17 +134,24 @@ export default async function AdminUsersPage({
const resolvedSearchParams = await searchParams; const resolvedSearchParams = await searchParams;
const requestedPage = Number(resolvedSearchParams?.page) || 1; const requestedPage = Number(resolvedSearchParams?.page) || 1;
const pageSize = 20;
const sortBy: SortBy = isSortBy(resolvedSearchParams?.sortBy) ? resolvedSearchParams.sortBy : 'joinedDate'; const sortBy: SortBy = isSortBy(resolvedSearchParams?.sortBy) ? resolvedSearchParams.sortBy : 'joinedDate';
const sortDirection: SortDirection = const sortDirection: SortDirection =
resolvedSearchParams?.sortDirection === 'asc' || resolvedSearchParams?.sortDirection === 'desc' resolvedSearchParams?.sortDirection === 'asc' || resolvedSearchParams?.sortDirection === 'desc'
? resolvedSearchParams.sortDirection ? resolvedSearchParams.sortDirection
: getDefaultSortDirection(sortBy); : getDefaultSortDirection(sortBy);
const pageSize = 20;
const [totalUsers, userStorage, userBunnyStorage, userDownloadEgress, bunnyStorageStats] = await Promise.all([
db.user.count(),
getCachedUserMediaStorage(),
getCachedUserBunnyStorage(),
getCachedUserDownloadEgress(),
getCachedBunnyStorageStats(),
]);
const totalPages = Math.max(1, Math.ceil(totalUsers / pageSize));
const page = Math.min(Math.max(1, requestedPage), totalPages);
const skip = (page - 1) * pageSize;
const [users, userStorage, userBunnyStorage, userDownloadEgress, bunnyStorageStats] = await Promise.all([ const select = {
db.user.findMany({
orderBy: { createdAt: 'desc' },
select: {
id: true, id: true,
name: true, name: true,
email: true, email: true,
@@ -117,13 +172,41 @@ export default async function AdminUsersPage({
comments: true, comments: true,
} }
} }
} } satisfies Prisma.UserSelect;
}),
getCachedUserMediaStorage(), let paginatedUsers: Array<{
getCachedUserBunnyStorage(), id: string;
getCachedUserDownloadEgress(), name: string | null;
getCachedBunnyStorageStats(), email: string | null;
]); createdAt: Date;
ownedWorkspaces: Array<{ _count: { members: number } }>;
_count: { ownedWorkspaces: number; projects: number; comments: number };
invitedMembersCount: number;
bunnyUploadBytes: number;
downloadEgressBytes: number;
mediaStorageBytes: number;
}> = [];
if (canSortInDb(sortBy)) {
const users = await db.user.findMany({
skip,
take: pageSize,
orderBy: getUsersOrderBy(sortBy, sortDirection),
select,
});
paginatedUsers = users.map((user) => ({
...user,
invitedMembersCount: user.ownedWorkspaces.reduce(
(total, workspace) => total + workspace._count.members,
0
),
bunnyUploadBytes: userBunnyStorage[user.id] || 0,
downloadEgressBytes: userDownloadEgress[user.id] || 0,
mediaStorageBytes: userStorage[user.id]?.total || 0,
}));
} else {
const users = await db.user.findMany({ select });
const usersWithMetrics = users.map((user) => ({ const usersWithMetrics = users.map((user) => ({
...user, ...user,
@@ -136,23 +219,11 @@ export default async function AdminUsersPage({
mediaStorageBytes: userStorage[user.id]?.total || 0, mediaStorageBytes: userStorage[user.id]?.total || 0,
})); }));
const sortedUsers = [...usersWithMetrics].sort((a, b) => { const sortedUsers = usersWithMetrics.sort((a, b) => {
let comparison = 0; let comparison = 0;
if (sortBy === 'user') { if (sortBy === 'invitedMembers') {
const aLabel = (a.name || a.email || 'Anonymous').toLowerCase();
const bLabel = (b.name || b.email || 'Anonymous').toLowerCase();
comparison = aLabel.localeCompare(bLabel);
} else if (sortBy === 'joinedDate') {
comparison = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
} else if (sortBy === 'workspacesOwned') {
comparison = a._count.ownedWorkspaces - b._count.ownedWorkspaces;
} else if (sortBy === 'invitedMembers') {
comparison = a.invitedMembersCount - b.invitedMembersCount; comparison = a.invitedMembersCount - b.invitedMembersCount;
} else if (sortBy === 'projectsOwned') {
comparison = a._count.projects - b._count.projects;
} else if (sortBy === 'totalComments') {
comparison = a._count.comments - b._count.comments;
} else if (sortBy === 'bunnyUpload') { } else if (sortBy === 'bunnyUpload') {
comparison = a.bunnyUploadBytes - b.bunnyUploadBytes; comparison = a.bunnyUploadBytes - b.bunnyUploadBytes;
} else if (sortBy === 'downloadEgress') { } else if (sortBy === 'downloadEgress') {
@@ -168,11 +239,8 @@ export default async function AdminUsersPage({
return sortDirection === 'asc' ? comparison : -comparison; return sortDirection === 'asc' ? comparison : -comparison;
}); });
const totalUsers = sortedUsers.length; paginatedUsers = sortedUsers.slice(skip, skip + pageSize);
const totalPages = Math.max(1, Math.ceil(totalUsers / pageSize)); }
const page = Math.min(Math.max(1, requestedPage), totalPages);
const skip = (page - 1) * pageSize;
const paginatedUsers = sortedUsers.slice(skip, skip + pageSize);
const buildUsersPageHref = ( const buildUsersPageHref = (
targetPage: number, targetPage: number,