mirror of
https://github.com/nfonteyne/octane-website.git
synced 2026-09-03 23:24:48 +02:00
update: propose repetion dates on the interface
This commit is contained in:
parent
324cc3c977
commit
21ba87d5ba
8 changed files with 314 additions and 2 deletions
10
src/db/migrations/013_rehearsals.sql
Normal file
10
src/db/migrations/013_rehearsals.sql
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
CREATE TABLE rehearsals (
|
||||
id SERIAL PRIMARY KEY,
|
||||
starts_at TIMESTAMPTZ NOT NULL,
|
||||
ends_at TIMESTAMPTZ NOT NULL,
|
||||
location TEXT,
|
||||
proposed_by INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_rehearsals_starts_at ON rehearsals (starts_at);
|
||||
33
src/repositories/rehearsalsRepo.js
Normal file
33
src/repositories/rehearsalsRepo.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
const pool = require('../db/pool');
|
||||
|
||||
async function findUpcoming() {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT r.id, r.starts_at, r.ends_at, r.location, r.proposed_by, u.name AS proposed_by_name, r.created_at
|
||||
FROM rehearsals r
|
||||
JOIN users u ON u.id = r.proposed_by
|
||||
WHERE r.ends_at >= now()
|
||||
ORDER BY r.starts_at
|
||||
`);
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function findById(id) {
|
||||
const { rows } = await pool.query('SELECT id, starts_at, ends_at, location, proposed_by FROM rehearsals WHERE id = $1', [id]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
async function create({ startsAt, endsAt, location, proposedBy }) {
|
||||
const { rows } = await pool.query(
|
||||
`INSERT INTO rehearsals (starts_at, ends_at, location, proposed_by)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id, starts_at, ends_at, location, proposed_by, created_at`,
|
||||
[startsAt, endsAt, location || null, proposedBy]
|
||||
);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
async function remove(id) {
|
||||
await pool.query('DELETE FROM rehearsals WHERE id = $1', [id]);
|
||||
}
|
||||
|
||||
module.exports = { findUpcoming, findById, create, remove };
|
||||
|
|
@ -7,6 +7,7 @@ const setlistsRoutes = require('./setlists');
|
|||
const musicSearchRoutes = require('./musicSearch');
|
||||
const calendarRoutes = require('./calendar');
|
||||
const adminRoutes = require('./admin');
|
||||
const rehearsalsRoutes = require('./rehearsals');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
|
|
@ -18,5 +19,6 @@ router.use('/setlists', setlistsRoutes);
|
|||
router.use('/music-search', musicSearchRoutes);
|
||||
router.use('/calendar', calendarRoutes);
|
||||
router.use('/admin', adminRoutes);
|
||||
router.use('/rehearsals', rehearsalsRoutes);
|
||||
|
||||
module.exports = router;
|
||||
|
|
|
|||
71
src/routes/rehearsals.js
Normal file
71
src/routes/rehearsals.js
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
const express = require('express');
|
||||
const rehearsalsRepo = require('../repositories/rehearsalsRepo');
|
||||
const asyncHandler = require('../lib/asyncHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req, res) => {
|
||||
const rehearsals = await rehearsalsRepo.findUpcoming();
|
||||
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,
|
||||
}))
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
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,
|
||||
});
|
||||
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);
|
||||
res.status(204).end();
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
Loading…
Add table
Add a link
Reference in a new issue