Sync can edit tournament perms UI/action perm check

This commit is contained in:
Kalle
2026-06-29 20:44:52 +03:00
parent 9759a896fe
commit dcbe92d1dd
7 changed files with 39 additions and 3 deletions

View File

@@ -44,6 +44,7 @@ export const action: ActionFunction = async ({ request }) => {
const isEditing = Boolean(data.eventToEditId);
const isAddingTournament = data.toToolsEnabled;
const isTournamentAdder = user.roles.includes("TOURNAMENT_ADDER");
const organizationId = data.organizationId
? Number(data.organizationId)
: null;
@@ -52,7 +53,7 @@ export const action: ActionFunction = async ({ request }) => {
await validateOrganization({
userId: user.id,
organizationId,
isTournamentAdder: user.roles.includes("TOURNAMENT_ADDER"),
isTournamentAdder,
});
} else if (!isEditing) {
requireRole(
@@ -141,7 +142,10 @@ export const action: ActionFunction = async ({ request }) => {
"Tournament has already started",
);
errorToastIfFalsy(tournament.isAdmin(user), "Not authorized");
errorToastIfFalsy(
tournament.canEditEventInfo(user, { isTournamentAdder }),
"Not authorized",
);
// once published, a tournament can't be flipped back to draft
if (!tournament.isDraft) {

View File

@@ -23,6 +23,7 @@ import { Redirect } from "~/components/Redirect";
import { DANGEROUS_CAN_ACCESS_DEV_CONTROLS } from "~/features/admin/core/dev-controls";
import { useUser } from "~/features/auth/core/user";
import { useTournament } from "~/features/tournament/routes/to.$id";
import { useHasRole } from "~/modules/permissions/hooks";
import {
calendarEventPage,
tournamentAdminPage,
@@ -42,6 +43,7 @@ export default function TournamentAdminLayout() {
const tournament = useTournament();
const outletContext = useOutletContext();
const user = useUser();
const isTournamentAdder = useHasRole("TOURNAMENT_ADDER");
const location = useLocation();
const showReopen = Boolean(
@@ -73,7 +75,8 @@ export default function TournamentAdminLayout() {
return (
<div className={clsx("stack lg", containerClassName("wide"))}>
{tournament.isAdmin(user) && !tournament.hasStarted ? (
{tournament.canEditEventInfo(user, { isTournamentAdder }) &&
!tournament.hasStarted ? (
<div className="stack horizontal items-end">
<LinkButton
to={tournamentEditPage(tournament.ctx.eventId)}

View File

@@ -1382,6 +1382,31 @@ export class Tournament {
return this.ctx.author.id === user.id;
}
/**
* Checks if the given user can edit the tournament's calendar event info.
*
* Mirrors the authorization enforced when the edit is submitted: organization
* admins can only edit when the organization is established, unless they have
* the TOURNAMENT_ADDER role.
*/
canEditEventInfo(
user: OptionalIdObject,
{ isTournamentAdder }: { isTournamentAdder: boolean },
) {
if (!user) return false;
if (isAdmin(user)) return true;
if (this.ctx.author.id === user.id) return true;
const isOrganizationAdmin = this.ctx.organization?.members.some(
(member) => member.userId === user.id && member.role === "ADMIN",
);
return Boolean(
isOrganizationAdmin &&
(isTournamentAdder || this.ctx.organization?.isEstablished),
);
}
/** Checks if the given user is an organizer of the tournament. */
isOrganizer(user: OptionalIdObject) {
if (!user) return false;

View File

@@ -6923,6 +6923,7 @@ export const LOW_INK_DECEMBER_2024 = (): TournamentData => ({
id: 3,
name: "Inkling Performance Labs",
slug: "inkling-performance-labs",
isEstablished: 1,
logoUrl: "fZrToLQrkqV3UZkdgwp0Q-1722263644749.webp",
series: [],
members: [

View File

@@ -2026,6 +2026,7 @@ export const SWIM_OR_SINK_167 = (
id: 3,
name: "Inkling Performance Labs",
slug: "inkling-performance-labs",
isEstablished: 1,
logoUrl: "fZrToLQrkqV3UZkdgwp0Q-1722263644749.webp",
series: [],
members: [

View File

@@ -85,6 +85,7 @@ export async function findById(id: number) {
"TournamentOrganization.id",
"TournamentOrganization.name",
"TournamentOrganization.slug",
"TournamentOrganization.isEstablished",
concatUserSubmittedImagePrefix(
innerEb.ref("UserSubmittedImage.url"),
).as("logoUrl"),

View File

@@ -782,6 +782,7 @@ describe("tournamentNameParts", () => {
id: 1,
name: "Sendou's Tournaments",
slug: "sendou",
isEstablished: 1,
logoUrl: null,
members: [],
series: [{ name: "In The Zone" }],