Skip to content

Commit 4f3358f

Browse files
authored
Add admin toggle (#46)
1 parent 95e69aa commit 4f3358f

File tree

9 files changed

+58
-15
lines changed

9 files changed

+58
-15
lines changed

api/src/models/leaderboard.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,21 @@ export class Leaderboard {
2020
format,
2121
tags,
2222
isAdmin,
23+
includeDisabled,
2324
}: {
2425
seasonId?: number;
2526
faction?: Faction;
2627
format?: Format;
2728
tags?: string[];
2829
isAdmin?: boolean;
30+
includeDisabled?: boolean;
2931
}): Promise<LeaderboardRow[]> {
3032
const results = await Results.getExpanded({
3133
seasonId,
3234
faction,
3335
format,
3436
tags,
37+
includeDisabled,
3538
});
3639

3740
const rows: Record<number, LeaderboardRow> = {};

api/src/models/results.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type GetExpandedOptions = {
3131
faction?: Faction | null;
3232
format?: Format | null;
3333
tags?: string[] | null;
34+
includeDisabled?: boolean;
3435
};
3536

3637
export async function get(user_id: number, tournament_id: number) {
@@ -56,6 +57,7 @@ export class Results {
5657
faction = null,
5758
format = null,
5859
tags = null,
60+
includeDisabled = false,
5961
}: GetExpandedOptions): Promise<ResultExpanded[]> {
6062
let tagModels: Tag[] = [];
6163
if (tags) {
@@ -87,8 +89,8 @@ export class Results {
8789
"tournaments.format as format",
8890
"tournaments.season_id as season_id",
8991
])
90-
// Only fetch results for non-disabled users
91-
.where("users.disabled", "=", 0)
92+
// Only fetch results for non-disabled users unless includeDisabled is true
93+
.$if(!includeDisabled, (qb) => qb.where("users.disabled", "=", 0))
9294
.select((eb) => [
9395
eb.fn
9496
.agg<number>("rank")

api/src/openapi.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ export const GetLeaderboardSchema = {
419419
factionCode: Query(z.string().optional()),
420420
format: Query(FormatComponent.optional()),
421421
tags: Query(z.string().or(z.array(z.string())).optional()),
422+
includeDisabled: Query(z.coerce.boolean().optional()),
422423
},
423424
responses: {
424425
"200": {

api/src/routes/leaderboard.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,24 @@ export class GetLeaderboard extends OpenAPIRoute {
5050
? [req.query.tags]
5151
: null;
5252

53+
// Security check: only admins can include disabled users
54+
if (req.query.includeDisabled === "true" && !req.is_admin) {
55+
return new Response("Forbidden: Only admins can view disabled users", {
56+
status: 403,
57+
});
58+
}
59+
60+
const includeDisabled =
61+
req.is_admin && req.query.includeDisabled === "true";
62+
5363
const rows: LeaderboardRowComponentType[] = [];
5464
const results = await Leaderboard.getExpanded({
5565
seasonId,
5666
faction,
5767
format,
5868
tags,
5969
isAdmin: req.is_admin,
70+
includeDisabled,
6071
});
6172
for (const result of results) {
6273
rows.push(LeaderboardRowComponent.parse(result));

app/src/client/services/LeaderboardService.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export class LeaderboardService {
2121
* @param factionCode
2222
* @param format
2323
* @param tags
24+
* @param includeDisabled
2425
* @returns LeaderboardRow Returns a array of rows compromising the full leaderboard for the given season
2526
* @throws ApiError
2627
*/
@@ -29,6 +30,7 @@ export class LeaderboardService {
2930
factionCode?: string,
3031
format?: (Format & string),
3132
tags?: (string | Array<string>),
33+
includeDisabled?: boolean | null,
3234
): CancelablePromise<Array<LeaderboardRow>> {
3335
return __request(OpenAPI, {
3436
method: 'GET',
@@ -38,6 +40,7 @@ export class LeaderboardService {
3840
'factionCode': factionCode,
3941
'format': format,
4042
'tags': tags,
43+
'includeDisabled': includeDisabled,
4144
},
4245
});
4346
}

app/src/output.css

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/src/routes/Profile.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ export function Profile() {
378378
>
379379
{tag.name}
380380
</button>
381-
<span className={"whitespace-nowrap text-sm text-gray-400"}>
381+
<span className={"whitespace-nowrap text-gray-400 text-sm"}>
382382
{tag.count} tournament{tag.count !== 1 ? "s" : ""}
383383
</span>
384384
</div>
@@ -387,7 +387,7 @@ export function Profile() {
387387
<div className={"mb-4 grid grid-cols-2 gap-4"}>
388388
{/* Left Column - Tournament Limits */}
389389
<div className={"flex flex-col gap-2"}>
390-
<label className={"text-sm font-medium text-gray-400"}>
390+
<label className={"font-medium text-gray-400 text-sm"}>
391391
Tournament Limits
392392
</label>
393393
<div className={"flex items-center"}>
@@ -413,12 +413,12 @@ export function Profile() {
413413

414414
{/* Right Column - Normalize Type */}
415415
<div className={"flex flex-col gap-2"}>
416-
<label className={"text-sm font-medium text-gray-400"}>
416+
<label className={"font-medium text-gray-400 text-sm"}>
417417
Normalize Type
418418
</label>
419419
<select
420420
className={
421-
"w-full rounded-lg border border-gray-600 bg-gray-900 px-3 py-2 text-sm text-gray-300"
421+
"w-full rounded-lg border border-gray-600 bg-gray-900 px-3 py-2 text-gray-300 text-sm"
422422
}
423423
value={tag.normalized_tournament_type || ""}
424424
onChange={(e) =>
@@ -440,7 +440,7 @@ export function Profile() {
440440
</div>
441441

442442
{/* Footer Row - Delete Button */}
443-
<div className={"flex justify-end border-t border-gray-700 pt-3"}>
443+
<div className={"flex justify-end border-gray-700 border-t pt-3"}>
444444
<Tooltip placement={"bottom"}>
445445
<TooltipTrigger>
446446
<button

app/src/routes/leaderboard.tsx

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { Link } from "../components/Link";
1818
import { PageHeading } from "../components/PageHeader";
1919
import { Tooltip, TooltipContent, TooltipTrigger } from "../components/Tooltip";
2020
import { getFilterValues, getSearchParamsFromValues } from "../filterUtils";
21+
import useAuth from "../useAuth";
2122
import { capStr } from "../util";
2223

2324
type ExpandedSectionProps = {
@@ -130,6 +131,8 @@ export function Leaderboard() {
130131
const [searchParams] = useSearchParams();
131132
const values = getFilterValues(searchParams);
132133
const [selectedUser, setSelectedUser] = useState<User | null>(null);
134+
const [includeDisabled, setIncludeDisabled] = useState(false);
135+
const { user } = useAuth();
133136

134137
const { data: leaderboardRows } = useQuery<LeaderboardRow[]>({
135138
queryKey: [
@@ -138,13 +141,15 @@ export function Leaderboard() {
138141
values.faction,
139142
values.format,
140143
values.tags,
144+
includeDisabled,
141145
],
142146
queryFn: () =>
143147
LeaderboardService.getGetLeaderboard(
144148
values.seasonId,
145149
values.faction,
146150
values.format,
147151
values.tags,
152+
includeDisabled,
148153
),
149154
});
150155

@@ -201,6 +206,25 @@ export function Leaderboard() {
201206
</h1>
202207
)}
203208
</PageHeading>
209+
{user?.is_admin && (
210+
<div className={"mb-4 flex items-center gap-2 px-4"}>
211+
<input
212+
type="checkbox"
213+
id="includeDisabled"
214+
checked={includeDisabled}
215+
onChange={(e) => setIncludeDisabled(e.target.checked)}
216+
className={
217+
"h-4 w-4 cursor-pointer rounded border-gray-600 bg-gray-900 text-cyan-500 focus:ring-2 focus:ring-cyan-500"
218+
}
219+
/>
220+
<label
221+
htmlFor="includeDisabled"
222+
className={"cursor-pointer text-gray-300 text-sm"}
223+
>
224+
Show all users (including disabled accounts)
225+
</label>
226+
</div>
227+
)}
204228
<FilterSection hasSearchBar={true} startSeason={3} />
205229
<table
206230
className={
@@ -277,9 +301,7 @@ export function Leaderboard() {
277301
)}
278302
>
279303
{row.disabled ? (
280-
<text className="text-gray-500 opacity-30">
281-
{row.user_name}
282-
</text>
304+
<text className="text-gray-500">{row.user_name}</text>
283305
) : (
284306
<Link to={getLinkToUserSearchParams(row)}>
285307
{row.user_name}

spec.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)