Skip to content

Commit

Permalink
Merge pull request #199 from Chupalika/what
Browse files Browse the repository at this point in the history
add a reschedule deadline to qualifiers to prevent late signups
  • Loading branch information
Chupalika authored Dec 8, 2024
2 parents 3475760 + 15cc5e6 commit ac14480
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 32 deletions.
89 changes: 68 additions & 21 deletions client/src/components/modules/Qualifiers.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class Qualifiers extends Component {

this.state = {
lobbies: [],
rescheduleDeadline: props.currentStage.rescheduleDeadline,
};
}

Expand Down Expand Up @@ -86,6 +87,24 @@ class Qualifiers extends Component {
}));
};

applyStageChanges = async (lobbyData) => {
const rescheduleDeadline = new Date(this.props.stripTimezone(lobbyData.rescheduleDeadline));
rescheduleDeadline.setUTCSeconds(0);
try {
const tourneyModel = await post("/api/stage", {
tourney: this.props.tourney,
stage: { ...this.props.currentStage, rescheduleDeadline },
index: this.props.currentStage.index,
});
this.setState((state) => ({
rescheduleDeadline: tourneyModel.rescheduleDeadline,
}));
message.success("Successfully updated stage");
} catch (e) {
message.error(e.message || e);
}
}

updateLobbyInState = (newLobby, key) => {
this.setState((state) => ({
lobbies: state.lobbies.map((m) => {
Expand Down Expand Up @@ -116,13 +135,25 @@ class Qualifiers extends Component {

// user is optional (only used for commentator)
remove = async (role, key, target) => {
const newLobby = await delet(`/api/lobby-${role}`, {
lobby: key,
target,
teams: this.props.teams,
tourney: this.props.tourney,
});
this.updateLobbyInState(newLobby, key);
// Make the update in case the request fails because the antd Tag is removed
// instantly and can't be added back unless the data itself is also updated
const theLobby = this.state.lobbies.find(lobby => lobby.key === key);
const updatedLobby = structuredClone(theLobby);
const updatedPlayers = updatedLobby.players.filter(player => player !== target);
updatedLobby.players = updatedPlayers;
this.updateLobbyInState(updatedLobby, key);
try {
const newLobby = await delet(`/api/lobby-${role}`, {
lobby: key,
target,
teams: this.props.teams,
tourney: this.props.tourney,
});
this.updateLobbyInState(newLobby, key);
} catch (e) {
message.error(e.message || e);
this.updateLobbyInState(theLobby, key);
}
};

addReferee = (key) => this.add("referee", key);
Expand Down Expand Up @@ -194,20 +225,36 @@ class Qualifiers extends Component {
return (
<>
{this.props.isAdmin() && (
<Collapse>
<Panel header={`Add new Qualifiers lobby`} key="1">
<Form name="basic" onFinish={this.onFinish}>
<Form.Item label="Lobby Time" name="time">
<DatePicker showTime format={"MM/DD HH:mm"} minuteStep={15} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Add
</Button>
</Form.Item>
</Form>
</Panel>
</Collapse>
<div className="admin-panel">
<Form name="editStage"
initialValues={{["rescheduleDeadline"]:moment(this.state.rescheduleDeadline).utcOffset(0)}}
onFinish={this.applyStageChanges}
layout="inline"
>
<Form.Item label="Reschedule Deadline" name="rescheduleDeadline">
<DatePicker showTime format={"MM/DD HH:mm"} minuteStep={15} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Save
</Button>
</Form.Item>
</Form>
<Collapse>
<Panel header={`Add new Qualifiers lobby`} key="1">
<Form name="basic" onFinish={this.onFinish}>
<Form.Item label="Lobby Time" name="time">
<DatePicker showTime format={"MM/DD HH:mm"} minuteStep={15} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Add
</Button>
</Form.Item>
</Form>
</Panel>
</Collapse>
</div>
)}

<div className="Schedule-list">
Expand Down
66 changes: 55 additions & 11 deletions server/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,6 @@ router.postAsync("/tournament", ensure.isAdmin, async (req, res) => {
* - stage: the new info for this stage
*/
router.postAsync("/stage", ensure.isPooler, async (req, res) => {
logger.info(`${req.user.username} updated stage ${req.body.index} of ${req.body.tourney}`);
const tourney = await Tournament.findOne({ code: req.body.tourney }).orFail();
tourney.stages[req.body.index].mappack = req.body.stage.mappack;
tourney.stages[req.body.index].poolVisible = req.body.stage.poolVisible;
Expand All @@ -660,13 +659,31 @@ router.postAsync("/stage", ensure.isPooler, async (req, res) => {
req.body.stage.statsVisible !== undefined &&
req.body.stage.statsVisible != (tourney.stages[req.body.index].statsVisible ?? false)
) {
if (!isAdmin(req.user, req.body.tourney))
if (!isAdmin(req.user, req.body.tourney)) {
logger.warn(`${req.user.username} attempted to toggle stage stats visibility`);
return res
.status(403)
.send({ error: "You don't have permission to toggle stage stats visibility" });
}
tourney.stages[req.body.index].statsVisible = req.body.stage.statsVisible;
}

// Only admin is allowed to change reschedule deadline
// (Only make this check when rescheduleDeadline is set in the request)
if (
req.body.stage.rescheduleDeadline !== undefined &&
new Date(req.body.stage.rescheduleDeadline).getTime() !== (tourney.stages[req.body.index].rescheduleDeadline.getTime())
) {
if (!isAdmin(req.user, req.body.tourney)) {
logger.warn(`${req.user.username} attempted to edit stage reschedule deadline`);
return res
.status(403)
.send({ error: "You don't have permission to edit stage reschedule deadline" });
}
tourney.stages[req.body.index].rescheduleDeadline = req.body.stage.rescheduleDeadline;
}

logger.info(`${req.user.username} updated stage ${req.body.index} of ${req.body.tourney}`);
await tourney.save();
res.send(tourney);
});
Expand Down Expand Up @@ -1084,30 +1101,45 @@ router.deleteAsync("/lobby-referee", ensure.isRef, async (req, res) => {
* - tourney: identifier of the tournament
*/
router.postAsync("/lobby-player", ensure.loggedIn, async (req, res) => {
logger.info(
`${req.user.username} signed ${req.body.user ?? "self"} up for quals lobby ${
req.body.lobby
} in ${req.body.tourney}`
);

// Prevent non-admin signing up another player
if (req.body.user && !isAdmin(req.user, req.body.tourney)) return res.status(403).send({});
if (req.body.user && !isAdmin(req.user, req.body.tourney)) {
logger.warn(`${req.user.username} attempted to sign another player up`);
return res.status(403).send({});
}
// Prevent non-registered player signing up self
if (!req.body.user && !req.user.tournies.includes(req.body.tourney))
if (!req.body.user && !req.user.tournies.includes(req.body.tourney)) {
logger.warn(`${req.user.username} attempted to sign up without being registered`);
return res.status(403).send({});
}

const tourney = await Tournament.findOne({ code: req.body.tourney });
const lobby = await QualifiersLobby.findOne({
_id: req.body.lobby,
tourney: req.body.tourney,
});

// Prevent non-admin signing up after the deadline
const qualifiersStage = tourney?.stages.find(stage => stage.name === "Qualifiers")
if (!isAdmin(req.user, req.body.tourney) && new Date() > (qualifiersStage!.rescheduleDeadline ?? new Date(0))) {
logger.warn(`${req.user.username} attempted to reschedule after the deadline`);
return res.status(403).send({ message: "The reschedule deadline has passed" });
}

// Prevent registered player signing up self for a full lobby
if (
tourney!.lobbyMaxSignups &&
!req.body.user &&
lobby!.players.length >= tourney!.lobbyMaxSignups
)
) {
logger.warn(`${req.user.username} attempted to sign up to a full lobby`);
return res.status(403).send({ message: "Lobby is full", updatedLobby: lobby });
}

logger.info(
`${req.user.username} signed ${req.body.user ?? "self"} up for quals lobby ${
req.body.lobby
} in ${req.body.tourney}`
);

const toAdd =
req.body.user ??
Expand Down Expand Up @@ -1137,6 +1169,18 @@ router.postAsync("/lobby-player", ensure.loggedIn, async (req, res) => {
*/
router.deleteAsync("/lobby-player", ensure.loggedIn, async (req, res) => {
if (!isAdmin(req.user, req.body.tourney)) {
// Prevent non-admin signing up after the deadline
const tourney = await Tournament.findOne({ code: req.body.tourney });
const lobby = await QualifiersLobby.findOne({
_id: req.body.lobby,
tourney: req.body.tourney,
});
const qualifiersStage = tourney?.stages.find(stage => stage.name === "Qualifiers")
if (new Date() > (qualifiersStage!.rescheduleDeadline ?? new Date(0))) {
logger.warn(`${req.user.username} attempted to reschedule after the deadline`);
return res.status(403).send({ message: "The reschedule deadline has passed" });
}

// makes sure the player has permission to do this

if (req.body.teams) {
Expand Down
2 changes: 2 additions & 0 deletions server/models/tournament.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface TourneyStage {
poolVisible: boolean;
mappack: string;
statsVisible: boolean;
rescheduleDeadline: Date;
}

interface ITournament {
Expand Down Expand Up @@ -39,6 +40,7 @@ const Tournament = new Schema<ITournament>({
poolVisible: Boolean,
mappack: String,
statsVisible: Boolean,
rescheduleDeadline: Date,
},
],
rankMin: Number,
Expand Down

0 comments on commit ac14480

Please sign in to comment.