2026-07-10 14:07:30 +02:00
|
|
|
const express = require('express');
|
|
|
|
|
const rehearsalsRepo = require('../repositories/rehearsalsRepo');
|
2026-07-31 11:51:05 +02:00
|
|
|
const calendarRepo = require('../repositories/calendarRepo');
|
2026-07-16 12:00:30 +02:00
|
|
|
const discord = require('../lib/discord');
|
2026-07-10 14:07:30 +02:00
|
|
|
const asyncHandler = require('../lib/asyncHandler');
|
2026-07-31 11:51:05 +02:00
|
|
|
const { computeRehearsalStatus } = require('../lib/rehearsalStatus');
|
2026-07-10 14:07:30 +02:00
|
|
|
|
|
|
|
|
const router = express.Router();
|
|
|
|
|
|
|
|
|
|
router.get(
|
|
|
|
|
'/',
|
|
|
|
|
asyncHandler(async (req, res) => {
|
2026-07-31 11:51:05 +02:00
|
|
|
const [rehearsals, settings] = await Promise.all([rehearsalsRepo.findUpcoming(), calendarRepo.getSlotSettings()]);
|
|
|
|
|
const threshold = settings.rehearsalConfirmThreshold;
|
2026-08-11 11:44:42 +02:00
|
|
|
const hostUserId = settings.rehearsalHostUserId;
|
2026-07-10 14:07:30 +02:00
|
|
|
res.json(
|
|
|
|
|
rehearsals.map((r) => ({
|
|
|
|
|
id: r.id,
|
|
|
|
|
startsAt: r.starts_at,
|
|
|
|
|
endsAt: r.ends_at,
|
|
|
|
|
location: r.location,
|
|
|
|
|
proposedBy: r.proposed_by,
|
|
|
|
|
proposedByName: r.proposed_by_name,
|
2026-07-15 17:25:32 +02:00
|
|
|
votes: r.votes,
|
2026-08-11 11:44:42 +02:00
|
|
|
status: computeRehearsalStatus(r.votes, threshold, hostUserId),
|
2026-07-31 11:51:05 +02:00
|
|
|
confirmThreshold: threshold,
|
2026-08-11 11:44:42 +02:00
|
|
|
hostUserId,
|
|
|
|
|
hostName: settings.rehearsalHostName,
|
2026-07-10 14:07:30 +02:00
|
|
|
}))
|
|
|
|
|
);
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
router.post(
|
|
|
|
|
'/',
|
|
|
|
|
asyncHandler(async (req, res) => {
|
|
|
|
|
const { startsAt, endsAt, location } = req.body || {};
|
|
|
|
|
if (!startsAt || !endsAt) {
|
|
|
|
|
return res.status(400).json({ error: 'starts_at_and_ends_at_required' });
|
|
|
|
|
}
|
|
|
|
|
const start = new Date(startsAt);
|
|
|
|
|
const end = new Date(endsAt);
|
|
|
|
|
if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) {
|
|
|
|
|
return res.status(400).json({ error: 'invalid_date' });
|
|
|
|
|
}
|
|
|
|
|
if (end <= start) {
|
|
|
|
|
return res.status(400).json({ error: 'ends_at_must_be_after_starts_at' });
|
|
|
|
|
}
|
|
|
|
|
if (start < new Date()) {
|
|
|
|
|
return res.status(400).json({ error: 'starts_at_must_be_in_the_future' });
|
|
|
|
|
}
|
|
|
|
|
const rehearsal = await rehearsalsRepo.create({
|
|
|
|
|
startsAt: start,
|
|
|
|
|
endsAt: end,
|
|
|
|
|
location: location ? location.trim() : null,
|
|
|
|
|
proposedBy: req.user.id,
|
|
|
|
|
});
|
2026-07-20 14:54:57 +02:00
|
|
|
await rehearsalsRepo.upsertVote(rehearsal.id, req.user.id, 'accept');
|
2026-07-16 12:00:30 +02:00
|
|
|
discord.notifyAll(
|
|
|
|
|
discord.rehearsalProposedMessage({ userName: req.user.name, startsAt: rehearsal.starts_at, location: rehearsal.location })
|
|
|
|
|
);
|
2026-07-10 14:07:30 +02:00
|
|
|
res.status(201).json({
|
|
|
|
|
id: rehearsal.id,
|
|
|
|
|
startsAt: rehearsal.starts_at,
|
|
|
|
|
endsAt: rehearsal.ends_at,
|
|
|
|
|
location: rehearsal.location,
|
|
|
|
|
proposedBy: rehearsal.proposed_by,
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
router.delete(
|
|
|
|
|
'/:id',
|
|
|
|
|
asyncHandler(async (req, res) => {
|
|
|
|
|
const rehearsal = await rehearsalsRepo.findById(req.params.id);
|
|
|
|
|
if (!rehearsal) return res.status(404).json({ error: 'not_found' });
|
|
|
|
|
if (rehearsal.proposed_by !== req.user.id && !req.user.is_admin) {
|
|
|
|
|
return res.status(403).json({ error: 'forbidden' });
|
|
|
|
|
}
|
|
|
|
|
await rehearsalsRepo.remove(req.params.id);
|
2026-07-16 12:00:30 +02:00
|
|
|
discord.notifyAll(discord.rehearsalRemovedMessage({ userName: req.user.name, startsAt: rehearsal.starts_at }));
|
2026-07-10 14:07:30 +02:00
|
|
|
res.status(204).end();
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
2026-07-15 17:25:32 +02:00
|
|
|
router.post(
|
|
|
|
|
'/:id/vote',
|
|
|
|
|
asyncHandler(async (req, res) => {
|
|
|
|
|
const { vote } = req.body || {};
|
|
|
|
|
if (!['accept', 'reject'].includes(vote)) {
|
|
|
|
|
return res.status(400).json({ error: 'invalid_vote' });
|
|
|
|
|
}
|
|
|
|
|
const rehearsal = await rehearsalsRepo.findById(req.params.id);
|
|
|
|
|
if (!rehearsal) return res.status(404).json({ error: 'not_found' });
|
|
|
|
|
const saved = await rehearsalsRepo.upsertVote(req.params.id, req.user.id, vote);
|
2026-07-16 12:00:30 +02:00
|
|
|
discord.notifyAll(discord.rehearsalVoteMessage({ userName: req.user.name, vote, startsAt: rehearsal.starts_at }));
|
2026-07-15 17:25:32 +02:00
|
|
|
res.json({
|
|
|
|
|
id: saved.id,
|
|
|
|
|
rehearsalId: saved.rehearsal_id,
|
|
|
|
|
userId: saved.user_id,
|
|
|
|
|
vote: saved.vote,
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
router.delete(
|
|
|
|
|
'/:id/vote',
|
|
|
|
|
asyncHandler(async (req, res) => {
|
|
|
|
|
await rehearsalsRepo.removeVote(req.params.id, req.user.id);
|
|
|
|
|
res.status(204).end();
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
2026-07-10 14:07:30 +02:00
|
|
|
module.exports = router;
|