/** * @copyright 2026 NHCarrigan * @license Naomi's Public License * @author Naomi Carrigan */ import type { LeaderboardResponse, SuggestionsLeaderboard, LikesLeaderboard, CommentsLeaderboard, OverallLeaderboard, } from "@library/shared-types"; import { prisma } from "../lib/prisma"; export class LeaderboardService { private prisma = prisma; constructor() {} /** * Get top users by suggestions submitted and accepted. */ async getTopSuggestions(limit = 25): Promise { const users = await this.prisma.user.findMany({ where: { profilePublic: true, isBanned: false }, include: { suggestions: { select: { status: true, }, }, }, }); const leaderboard = users .map((user) => { const totalSuggestions = user.suggestions.length; const acceptedSuggestions = user.suggestions.filter( (s) => s.status === "ACCEPTED" ).length; const acceptanceRate = totalSuggestions > 0 ? Math.round((acceptedSuggestions / totalSuggestions) * 100) : 0; return { id: user.id, username: user.username, slug: user.slug, avatar: user.avatar, primaryBadge: user.primaryBadge, isVip: user.isVip, isMod: user.isMod, isStaff: user.isStaff, createdAt: user.createdAt, totalSuggestions, acceptedSuggestions, acceptanceRate, }; }) .filter((user) => user.totalSuggestions > 0) .sort((a, b) => { if (b.totalSuggestions !== a.totalSuggestions) { return b.totalSuggestions - a.totalSuggestions; } return b.acceptanceRate - a.acceptanceRate; }) .slice(0, limit); return leaderboard; } /** * Get top users by likes given. */ async getTopLikes(limit = 25): Promise { const users = await this.prisma.user.findMany({ where: { profilePublic: true, isBanned: false }, include: { likes: true, }, }); const leaderboard = users .map((user) => ({ id: user.id, username: user.username, slug: user.slug, avatar: user.avatar, primaryBadge: user.primaryBadge, isVip: user.isVip, isMod: user.isMod, isStaff: user.isStaff, createdAt: user.createdAt, totalLikes: user.likes.length, })) .filter((user) => user.totalLikes > 0) .sort((a, b) => b.totalLikes - a.totalLikes) .slice(0, limit); return leaderboard; } /** * Get top users by comments posted. */ async getTopComments(limit = 25): Promise { const users = await this.prisma.user.findMany({ where: { profilePublic: true, isBanned: false }, include: { comments: true, }, }); const leaderboard = users .map((user) => ({ id: user.id, username: user.username, slug: user.slug, avatar: user.avatar, primaryBadge: user.primaryBadge, isVip: user.isVip, isMod: user.isMod, isStaff: user.isStaff, createdAt: user.createdAt, totalComments: user.comments.length, })) .filter((user) => user.totalComments > 0) .sort((a, b) => b.totalComments - a.totalComments) .slice(0, limit); return leaderboard; } /** * Get overall leaderboard based on combined engagement. */ async getOverallLeaderboard(limit = 25): Promise { const users = await this.prisma.user.findMany({ where: { profilePublic: true, isBanned: false }, include: { suggestions: true, likes: true, comments: true, userAchievements: true, }, }); const leaderboard = users .map((user) => { const totalSuggestions = user.suggestions.length; const totalLikes = user.likes.length; const totalComments = user.comments.length; const achievementCount = user.userAchievements.length; const diversityScore = (totalSuggestions > 0 ? 1 : 0) + (totalLikes > 0 ? 1 : 0) + (totalComments > 0 ? 1 : 0); return { id: user.id, username: user.username, slug: user.slug, avatar: user.avatar, primaryBadge: user.primaryBadge, isVip: user.isVip, isMod: user.isMod, isStaff: user.isStaff, createdAt: user.createdAt, totalSuggestions, totalLikes, totalComments, achievementCount, achievementPoints: user.achievementPoints, currentStreak: user.currentStreak, diversityScore, }; }) .filter( (user) => user.totalSuggestions > 0 || user.totalLikes > 0 || user.totalComments > 0 ) .sort((a, b) => { if (b.achievementPoints !== a.achievementPoints) { return b.achievementPoints - a.achievementPoints; } if (b.diversityScore !== a.diversityScore) { return b.diversityScore - a.diversityScore; } const totalA = a.totalSuggestions + a.totalLikes + a.totalComments; const totalB = b.totalSuggestions + b.totalLikes + b.totalComments; return totalB - totalA; }) .slice(0, limit); return leaderboard; } /** * Get all leaderboards at once. */ async getAllLeaderboards(limit = 25): Promise { const [topSuggestions, topLikes, topComments, topOverall] = await Promise.all([ this.getTopSuggestions(limit), this.getTopLikes(limit), this.getTopComments(limit), this.getOverallLeaderboard(limit), ]); return { topSuggestions, topLikes, topComments, topOverall, }; } }