Add sorting to badges owned widget

This commit is contained in:
Kalle 2026-02-17 21:29:12 +02:00
parent e8c5ab6c07
commit 0936e70e73
4 changed files with 132 additions and 45 deletions

View File

@ -2,6 +2,7 @@ import type { ExpressionBuilder, NotNull } from "kysely";
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/sqlite";
import { db } from "~/db/sql";
import type { DB } from "~/db/tables";
import { sortBadgesByFavorites } from "~/features/user-page/core/badge-sorting.server";
import invariant from "~/utils/invariant";
import { COMMON_USER_FIELDS } from "~/utils/kysely.server";
import { SPLATOON_3_XP_BADGE_VALUES } from "./badges-constants";
@ -110,20 +111,35 @@ export function findManagedByUserId(userId: number) {
.execute();
}
export function findByOwnerUserId(userId: number) {
return db
export async function findByOwnerUserId(userId: number) {
const rows = await db
.selectFrom("BadgeOwner")
.innerJoin("Badge", "Badge.id", "BadgeOwner.badgeId")
.innerJoin("User", "User.id", "BadgeOwner.userId")
.select(({ fn }) => [
fn.count<number>("BadgeOwner.badgeId").as("count"),
"Badge.id",
"Badge.displayName",
"Badge.code",
"Badge.hue",
"User.favoriteBadgeIds",
"User.patronTier",
])
.where("BadgeOwner.userId", "=", userId)
.groupBy(["BadgeOwner.badgeId", "BadgeOwner.userId"])
.execute();
if (rows.length === 0) return [];
const { favoriteBadgeIds, patronTier } = rows[0];
return sortBadgesByFavorites({
favoriteBadgeIds,
badges: rows.map(
({ favoriteBadgeIds: _, patronTier: __, ...badge }) => badge,
),
patronTier,
}).badges;
}
export function findByAuthorUserId(userId: number) {

View File

@ -25,6 +25,7 @@ import { logger } from "~/utils/logger";
import { safeNumberParse } from "~/utils/number";
import { bskyUrl, twitchUrl, youtubeUrl } from "~/utils/urls";
import type { ChatUser } from "../chat/chat-types";
import { sortBadgesByFavorites } from "./core/badge-sorting.server";
import { findWidgetById } from "./core/widgets/portfolio";
import { WIDGET_LOADERS } from "./core/widgets/portfolio-loaders.server";
import type { LoadedWidget } from "./core/widgets/types";
@ -242,27 +243,12 @@ export async function findProfileByIdentifier(
return null;
}
const favoriteBadgeIds = favoriteBadgesOwnedAndSupporterStatusAdjusted(row);
return {
...row,
team: row.teams.find((t) => t.isMainTeam),
secondaryTeams: row.teams.filter((t) => !t.isMainTeam),
teams: undefined,
favoriteBadgeIds,
badges: row.badges.sort((a, b) => {
const aIdx = favoriteBadgeIds?.indexOf(a.id) ?? -1;
const bIdx = favoriteBadgeIds?.indexOf(b.id) ?? -1;
if (aIdx !== bIdx) {
if (aIdx === -1) return 1;
if (bIdx === -1) return -1;
return aIdx - bIdx;
}
return b.id - a.id;
}),
...sortBadgesByFavorites(row),
discordUniqueName:
forceShowDiscordUniqueName || row.showDiscordUniqueName
? row.discordUniqueName
@ -366,33 +352,6 @@ export async function widgetsByUserId(
return loadedWidgets.filter((w) => w !== null);
}
function favoriteBadgesOwnedAndSupporterStatusAdjusted(row: {
favoriteBadgeIds: number[] | null;
badges: Array<{
id: number;
}>;
patronTier: number | null;
}) {
// filter out favorite badges no longer owner of
let favoriteBadgeIds =
row.favoriteBadgeIds?.filter((badgeId) =>
row.badges.some((badge) => badge.id === badgeId),
) ?? null;
if (favoriteBadgeIds?.length === 0) {
favoriteBadgeIds = null;
}
// non-supporters can only have one favorite badge, handle losing supporter status
favoriteBadgeIds = isSupporter(row)
? favoriteBadgeIds
: favoriteBadgeIds
? [favoriteBadgeIds[0]]
: null;
return favoriteBadgeIds;
}
export function findByCustomUrl(customUrl: string) {
return db
.selectFrom("User")

View File

@ -0,0 +1,63 @@
import { describe, expect, it } from "vitest";
import { sortBadgesByFavorites } from "./badge-sorting.server";
const badge = (id: number) => ({
id,
displayName: `Badge ${id}`,
code: `b${id}`,
});
describe("sortBadgesByFavorites", () => {
it("returns badges sorted by descending id when no favorites", () => {
const result = sortBadgesByFavorites({
favoriteBadgeIds: null,
badges: [badge(1), badge(3), badge(2)],
patronTier: null,
});
expect(result.badges.map((b) => b.id)).toEqual([3, 2, 1]);
expect(result.favoriteBadgeIds).toBeNull();
});
it("places favorites first in order for supporters", () => {
const result = sortBadgesByFavorites({
favoriteBadgeIds: [2, 1],
badges: [badge(1), badge(2), badge(3)],
patronTier: 2,
});
expect(result.badges.map((b) => b.id)).toEqual([2, 1, 3]);
expect(result.favoriteBadgeIds).toEqual([2, 1]);
});
it("limits non-supporters to one favorite", () => {
const result = sortBadgesByFavorites({
favoriteBadgeIds: [2, 3],
badges: [badge(1), badge(2), badge(3)],
patronTier: null,
});
expect(result.favoriteBadgeIds).toEqual([2]);
expect(result.badges[0].id).toBe(2);
});
it("filters out unowned favorite badge ids", () => {
const result = sortBadgesByFavorites({
favoriteBadgeIds: [99, 1],
badges: [badge(1), badge(2)],
patronTier: 2,
});
expect(result.favoriteBadgeIds).toEqual([1]);
});
it("returns null favoriteBadgeIds when all favorites are unowned", () => {
const result = sortBadgesByFavorites({
favoriteBadgeIds: [99],
badges: [badge(1), badge(2)],
patronTier: 2,
});
expect(result.favoriteBadgeIds).toBeNull();
});
});

View File

@ -0,0 +1,49 @@
import { isSupporter } from "~/modules/permissions/utils";
interface SortBadgesByFavoritesArgs<T extends { id: number }[]> {
favoriteBadgeIds: number[] | null;
badges: T;
patronTier: number | null;
}
export function sortBadgesByFavorites<T extends { id: number }[]>({
favoriteBadgeIds,
badges,
patronTier,
}: SortBadgesByFavoritesArgs<T>): {
badges: T;
favoriteBadgeIds: number[] | null;
} {
// filter out favorite badges no longer owner of
let filteredFavoriteIds =
favoriteBadgeIds?.filter((badgeId) =>
badges.some((badge) => badge.id === badgeId),
) ?? null;
if (filteredFavoriteIds?.length === 0) {
filteredFavoriteIds = null;
}
filteredFavoriteIds = isSupporter({ patronTier })
? filteredFavoriteIds
: filteredFavoriteIds
? [filteredFavoriteIds[0]]
: null;
// non-supporters can only have one favorite badge, handle losing supporter status
const sortedBadges = badges.toSorted((a, b) => {
const aIdx = filteredFavoriteIds?.indexOf(a.id) ?? -1;
const bIdx = filteredFavoriteIds?.indexOf(b.id) ?? -1;
if (aIdx !== bIdx) {
if (aIdx === -1) return 1;
if (bIdx === -1) return -1;
return aIdx - bIdx;
}
return b.id - a.id;
}) as T;
return { badges: sortedBadges, favoriteBadgeIds: filteredFavoriteIds };
}