diff --git a/app/features/calendar/actions/calendar.$id.server.ts b/app/features/calendar/actions/calendar.$id.server.ts index a90a245f7..aa1c6dd8f 100644 --- a/app/features/calendar/actions/calendar.$id.server.ts +++ b/app/features/calendar/actions/calendar.$id.server.ts @@ -1,12 +1,19 @@ import type { ActionFunction } from "react-router"; import { redirect } from "react-router"; import * as v from "valibot"; +import { requireUser } from "~/features/auth/core/user.server"; import * as CalendarRepository from "~/features/calendar/CalendarRepository.server"; import * as ShowcaseTournaments from "~/features/front-page/core/ShowcaseTournaments.server"; -import * as BracketRepository from "~/features/tournament-bracket/BracketRepository.server"; -import { clearTournamentDataCache } from "~/features/tournament-bracket/core/Tournament.server"; +import { + clearTournamentDataCache, + tournamentFromDB, +} from "~/features/tournament-bracket/core/Tournament.server"; import { requirePermission } from "~/modules/permissions/guards.server"; -import { errorToastIfFalsy, notFoundIfNullish } from "~/utils/remix.server"; +import { + errorToastIfFalsy, + forbidden, + notFoundIfNullish, +} from "~/utils/remix.server"; import { actualNumber, id, preprocess } from "~/utils/schema"; import { CALENDAR_PAGE } from "~/utils/urls"; @@ -19,14 +26,17 @@ export const action: ActionFunction = async ({ params }) => { await CalendarRepository.findById(parsedParams.id), ); - requirePermission(event, "DELETE"); - if (event.tournamentId) { - errorToastIfFalsy( - (await BracketRepository.findByTournamentId(event.tournamentId)).stage - .length === 0, - "Tournament has already started", - ); + const user = requireUser(); + const tournament = await tournamentFromDB(event.tournamentId); + + if (!tournament.canEditEventInfo(user)) { + throw forbidden(); + } + + errorToastIfFalsy(!tournament.hasStarted, "Tournament has already started"); + } else { + requirePermission(event, "DELETE"); } await CalendarRepository.deleteById({ diff --git a/app/features/calendar/routes/calendar.$id.test.ts b/app/features/calendar/routes/calendar.$id.test.ts index 4fd68e6ad..594532575 100644 --- a/app/features/calendar/routes/calendar.$id.test.ts +++ b/app/features/calendar/routes/calendar.$id.test.ts @@ -1,7 +1,8 @@ -import { addDays } from "date-fns"; +import { addDays, subDays } from "date-fns"; import type * as v from "valibot"; import { describe, expect, test } from "vitest"; import * as TournamentFactory from "~/db/seed/factories/TournamentFactory"; +import * as TournamentOrganizationFactory from "~/db/seed/factories/TournamentOrganizationFactory"; import * as UserFactory from "~/db/seed/factories/UserFactory"; import * as CalendarRepository from "~/features/calendar/CalendarRepository.server"; import { dateToDatabaseTimestamp } from "~/utils/dates"; @@ -47,4 +48,43 @@ describe("calendar event deletion", () => { const eventAfter = await CalendarRepository.findById(tournament.eventId); expect(eventAfter).toBeNull(); }); + + test("lets the author delete a tournament whose start time passed without it ever starting", async () => { + await UserFactory.createAdmin(); + const regular = await UserFactory.createRegular(); + const tournament = await TournamentFactory.create({ + authorId: regular.id, + startTimes: [dateToDatabaseTimestamp(subDays(new Date(), 2))], + }); + + await deleteAction( + {}, + { user: "regular", params: { id: String(tournament.eventId) } }, + ); + + const eventAfter = await CalendarRepository.findById(tournament.eventId); + expect(eventAfter).toBeNull(); + }); + + test("lets an admin of an established organization delete its tournament", async () => { + const admin = await UserFactory.createAdmin(); + await UserFactory.createRegular(); + const orgAdmin = await UserFactory.create(); + const organization = await TournamentOrganizationFactory.create( + { ownerId: orgAdmin.id }, + { isEstablished: true }, + ); + const tournament = await TournamentFactory.create({ + authorId: admin.id, + organizationId: organization.id, + }); + + await deleteAction( + {}, + { user: orgAdmin.id, params: { id: String(tournament.eventId) } }, + ); + + const eventAfter = await CalendarRepository.findById(tournament.eventId); + expect(eventAfter).toBeNull(); + }); });