refactor(profile): isolate resilient guild reads
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import type { DiscordMemberProfile } from "@/lib/discord/discord";
|
||||
import type { DiscordVoiceChannel } from "@/lib/leaderboard/discord";
|
||||
import type {
|
||||
getMemberVoiceStats,
|
||||
getMemberXpStats,
|
||||
} from "@/lib/leaderboard/database";
|
||||
import { buildLevelProgress, type LevelProgress } from "@/lib/leaderboard/level-progress";
|
||||
|
||||
export type GuildProfileXp = LevelProgress & { rank: number | null };
|
||||
|
||||
export type GuildProfileVoice = Awaited<ReturnType<typeof getMemberVoiceStats>>;
|
||||
|
||||
export type GuildProfileRead = {
|
||||
profile: DiscordMemberProfile | null;
|
||||
profileUnavailable: boolean;
|
||||
xp: GuildProfileXp | null;
|
||||
voice: GuildProfileVoice | null;
|
||||
};
|
||||
|
||||
export type GuildProfileSources = {
|
||||
getProfile: (discordId: string) => Promise<DiscordMemberProfile | null>;
|
||||
getXp: (discordId: string) => ReturnType<typeof getMemberXpStats>;
|
||||
getVoice: (
|
||||
discordId: string,
|
||||
currentChannelIds: string[],
|
||||
) => ReturnType<typeof getMemberVoiceStats>;
|
||||
getCurrentVoiceChannels: () => Promise<DiscordVoiceChannel[]>;
|
||||
reportFailure?: (source: "profile" | "xp" | "voice", error: unknown) => void;
|
||||
};
|
||||
|
||||
function defaultReportFailure(source: string, error: unknown) {
|
||||
console.error(`Failed to load own Guild profile ${source} data`, error);
|
||||
}
|
||||
|
||||
export function createGuildProfileReader(
|
||||
sources: GuildProfileSources,
|
||||
) {
|
||||
return async function loadGuildProfile(discordId: string): Promise<GuildProfileRead> {
|
||||
const reportFailure = sources.reportFailure ?? defaultReportFailure;
|
||||
const [profileResult, xpResult, voiceResult] = await Promise.allSettled([
|
||||
sources.getProfile(discordId),
|
||||
sources.getXp(discordId),
|
||||
sources.getCurrentVoiceChannels().then((channels) =>
|
||||
sources.getVoice(discordId, channels.map((channel) => channel.id)),
|
||||
),
|
||||
]);
|
||||
|
||||
if (profileResult.status === "rejected") {
|
||||
reportFailure("profile", profileResult.reason);
|
||||
}
|
||||
if (xpResult.status === "rejected") {
|
||||
reportFailure("xp", xpResult.reason);
|
||||
}
|
||||
if (voiceResult.status === "rejected") {
|
||||
reportFailure("voice", voiceResult.reason);
|
||||
}
|
||||
|
||||
const profile = profileResult.status === "fulfilled" ? profileResult.value : null;
|
||||
const xpStats = xpResult.status === "fulfilled" ? xpResult.value : null;
|
||||
const voice = voiceResult.status === "fulfilled" ? voiceResult.value : null;
|
||||
|
||||
return {
|
||||
profile,
|
||||
profileUnavailable: !profile,
|
||||
xp: xpStats
|
||||
? {
|
||||
...buildLevelProgress(xpStats.level, xpStats.totalXp),
|
||||
rank: xpStats.rank,
|
||||
}
|
||||
: null,
|
||||
voice,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadGuildProfile(discordId: string) {
|
||||
const [discord, leaderboardDiscord, leaderboardDatabase] = await Promise.all([
|
||||
import("@/lib/discord/discord"),
|
||||
import("@/lib/leaderboard/discord"),
|
||||
import("@/lib/leaderboard/database"),
|
||||
]);
|
||||
|
||||
return createGuildProfileReader({
|
||||
getProfile: discord.getDiscordMemberProfile,
|
||||
getXp: leaderboardDatabase.getMemberXpStats,
|
||||
getVoice: leaderboardDatabase.getMemberVoiceStats,
|
||||
getCurrentVoiceChannels: leaderboardDiscord.getCurrentDiscordVoiceChannels,
|
||||
})(discordId);
|
||||
}
|
||||
Reference in New Issue
Block a user