mirror of
https://github.com/nfonteyne/octane-website.git
synced 2026-09-03 23:24:48 +02:00
first commit
This commit is contained in:
commit
244cdbedd7
44 changed files with 3386 additions and 0 deletions
16
src/routes/index.js
Normal file
16
src/routes/index.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
const express = require('express');
|
||||
const usersRoutes = require('./users');
|
||||
const instrumentsRoutes = require('./instruments');
|
||||
const songsRoutes = require('./songs');
|
||||
const suggestionsRoutes = require('./suggestions');
|
||||
const setlistsRoutes = require('./setlists');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use('/users', usersRoutes);
|
||||
router.use('/instruments', instrumentsRoutes);
|
||||
router.use('/songs', songsRoutes);
|
||||
router.use('/suggestions', suggestionsRoutes);
|
||||
router.use('/setlists', setlistsRoutes);
|
||||
|
||||
module.exports = router;
|
||||
15
src/routes/instruments.js
Normal file
15
src/routes/instruments.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
const express = require('express');
|
||||
const instrumentsRepo = require('../repositories/instrumentsRepo');
|
||||
const asyncHandler = require('../lib/asyncHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req, res) => {
|
||||
const instruments = await instrumentsRepo.findAll();
|
||||
res.json(instruments);
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
123
src/routes/setlists.js
Normal file
123
src/routes/setlists.js
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
const express = require('express');
|
||||
const setlistsRepo = require('../repositories/setlistsRepo');
|
||||
const { requireAdmin } = require('../auth/middleware');
|
||||
const asyncHandler = require('../lib/asyncHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
async function withSongs(setlist) {
|
||||
if (!setlist) return null;
|
||||
const songs = await setlistsRepo.findSongs(setlist.id);
|
||||
return { ...setlist, songs };
|
||||
}
|
||||
|
||||
router.get(
|
||||
'/next',
|
||||
asyncHandler(async (req, res) => {
|
||||
const setlist = await setlistsRepo.findNext();
|
||||
res.json(await withSongs(setlist));
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/history',
|
||||
asyncHandler(async (req, res) => {
|
||||
res.json(await setlistsRepo.findHistory());
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:id',
|
||||
asyncHandler(async (req, res) => {
|
||||
const setlist = await setlistsRepo.findById(req.params.id);
|
||||
if (!setlist) return res.status(404).json({ error: 'not_found' });
|
||||
res.json(await withSongs(setlist));
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
requireAdmin,
|
||||
asyncHandler(async (req, res) => {
|
||||
const { name, venue, concertDate } = req.body || {};
|
||||
if (!concertDate) return res.status(400).json({ error: 'concert_date_required' });
|
||||
const setlist = await setlistsRepo.create({ name, venue, concertDate, createdBy: req.user.id });
|
||||
res.status(201).json(setlist);
|
||||
})
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:id',
|
||||
requireAdmin,
|
||||
asyncHandler(async (req, res) => {
|
||||
const { name, venue, concertDate } = req.body || {};
|
||||
if (!concertDate) return res.status(400).json({ error: 'concert_date_required' });
|
||||
const setlist = await setlistsRepo.update(req.params.id, { name, venue, concertDate });
|
||||
if (!setlist) return res.status(404).json({ error: 'not_found' });
|
||||
res.json(setlist);
|
||||
})
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:id',
|
||||
requireAdmin,
|
||||
asyncHandler(async (req, res) => {
|
||||
await setlistsRepo.remove(req.params.id);
|
||||
res.status(204).end();
|
||||
})
|
||||
);
|
||||
|
||||
router.put(
|
||||
'/:id/songs',
|
||||
requireAdmin,
|
||||
asyncHandler(async (req, res) => {
|
||||
const { songs } = req.body || {};
|
||||
if (!Array.isArray(songs)) return res.status(400).json({ error: 'songs_array_required' });
|
||||
for (const s of songs) {
|
||||
if (!s.songId || typeof s.position !== 'number') {
|
||||
return res.status(400).json({ error: 'invalid_song_entry' });
|
||||
}
|
||||
}
|
||||
try {
|
||||
await setlistsRepo.replaceSongs(req.params.id, songs);
|
||||
} catch (err) {
|
||||
if (err.code === '23505') {
|
||||
return res.status(409).json({ error: 'duplicate_position', message: 'Deux morceaux ont la même position.' });
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
const setlist = await setlistsRepo.findById(req.params.id);
|
||||
res.json(await withSongs(setlist));
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/:id/songs',
|
||||
requireAdmin,
|
||||
asyncHandler(async (req, res) => {
|
||||
const { songId, position, note, isEncore } = req.body || {};
|
||||
if (!songId || typeof position !== 'number') {
|
||||
return res.status(400).json({ error: 'invalid_song_entry' });
|
||||
}
|
||||
try {
|
||||
const entry = await setlistsRepo.addSong(req.params.id, { songId, position, note, isEncore });
|
||||
res.status(201).json(entry);
|
||||
} catch (err) {
|
||||
if (err.code === '23505') {
|
||||
return res.status(409).json({ error: 'duplicate_position', message: 'Ce morceau ou cette position est déjà utilisé.' });
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:id/songs/:setlistSongId',
|
||||
requireAdmin,
|
||||
asyncHandler(async (req, res) => {
|
||||
await setlistsRepo.removeSong(req.params.id, req.params.setlistSongId);
|
||||
res.status(204).end();
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
99
src/routes/songs.js
Normal file
99
src/routes/songs.js
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
const express = require('express');
|
||||
const songsRepo = require('../repositories/songsRepo');
|
||||
const { requireAdmin } = require('../auth/middleware');
|
||||
const asyncHandler = require('../lib/asyncHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req, res) => {
|
||||
res.json(await songsRepo.findAll());
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:id',
|
||||
asyncHandler(async (req, res) => {
|
||||
const song = await songsRepo.findById(req.params.id);
|
||||
if (!song) return res.status(404).json({ error: 'not_found' });
|
||||
const tutorials = await songsRepo.findTutorials(req.params.id);
|
||||
res.json({ ...song, tutorials });
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
requireAdmin,
|
||||
asyncHandler(async (req, res) => {
|
||||
const { title, artist, notes } = req.body || {};
|
||||
if (!title || !title.trim() || !artist || !artist.trim()) {
|
||||
return res.status(400).json({ error: 'title_and_artist_required' });
|
||||
}
|
||||
const song = await songsRepo.create({ title: title.trim(), artist: artist.trim(), notes, addedBy: req.user.id });
|
||||
res.status(201).json(song);
|
||||
})
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:id',
|
||||
requireAdmin,
|
||||
asyncHandler(async (req, res) => {
|
||||
const { title, artist, notes } = req.body || {};
|
||||
if (!title || !title.trim() || !artist || !artist.trim()) {
|
||||
return res.status(400).json({ error: 'title_and_artist_required' });
|
||||
}
|
||||
const song = await songsRepo.update(req.params.id, { title: title.trim(), artist: artist.trim(), notes });
|
||||
if (!song) return res.status(404).json({ error: 'not_found' });
|
||||
res.json(song);
|
||||
})
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:id',
|
||||
requireAdmin,
|
||||
asyncHandler(async (req, res) => {
|
||||
try {
|
||||
await songsRepo.remove(req.params.id);
|
||||
res.status(204).end();
|
||||
} catch (err) {
|
||||
if (err.code === '23503') {
|
||||
return res
|
||||
.status(409)
|
||||
.json({ error: 'song_in_use', message: 'Ce morceau est utilisé dans une setlist et ne peut pas être supprimé.' });
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/:id/tutorials',
|
||||
requireAdmin,
|
||||
asyncHandler(async (req, res) => {
|
||||
const { instrumentId, url, label } = req.body || {};
|
||||
if (!instrumentId || !url || !url.trim()) {
|
||||
return res.status(400).json({ error: 'instrument_and_url_required' });
|
||||
}
|
||||
const song = await songsRepo.findById(req.params.id);
|
||||
if (!song) return res.status(404).json({ error: 'not_found' });
|
||||
const tutorial = await songsRepo.addTutorial(req.params.id, {
|
||||
instrumentId,
|
||||
url: url.trim(),
|
||||
label,
|
||||
addedBy: req.user.id,
|
||||
});
|
||||
res.status(201).json(tutorial);
|
||||
})
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:songId/tutorials/:id',
|
||||
requireAdmin,
|
||||
asyncHandler(async (req, res) => {
|
||||
await songsRepo.removeTutorial(req.params.songId, req.params.id);
|
||||
res.status(204).end();
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
101
src/routes/suggestions.js
Normal file
101
src/routes/suggestions.js
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
const express = require('express');
|
||||
const suggestionsRepo = require('../repositories/suggestionsRepo');
|
||||
const { requireAdmin } = require('../auth/middleware');
|
||||
const { isValidYoutubeUrl } = require('../lib/youtube');
|
||||
const asyncHandler = require('../lib/asyncHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req, res) => {
|
||||
res.json(await suggestionsRepo.findAll());
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:id',
|
||||
asyncHandler(async (req, res) => {
|
||||
const suggestion = await suggestionsRepo.findById(req.params.id);
|
||||
if (!suggestion) return res.status(404).json({ error: 'not_found' });
|
||||
const votes = await suggestionsRepo.findVotes(req.params.id);
|
||||
res.json({ ...suggestion, votes });
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
asyncHandler(async (req, res) => {
|
||||
const { title, artist, youtubeUrl } = req.body || {};
|
||||
if (!title || !title.trim() || !youtubeUrl || !youtubeUrl.trim()) {
|
||||
return res.status(400).json({ error: 'title_and_youtube_url_required' });
|
||||
}
|
||||
if (!isValidYoutubeUrl(youtubeUrl.trim())) {
|
||||
return res.status(400).json({ error: 'invalid_youtube_url' });
|
||||
}
|
||||
const suggestion = await suggestionsRepo.create({
|
||||
title: title.trim(),
|
||||
artist: artist ? artist.trim() : null,
|
||||
youtubeUrl: youtubeUrl.trim(),
|
||||
suggestedBy: req.user.id,
|
||||
});
|
||||
res.status(201).json(suggestion);
|
||||
})
|
||||
);
|
||||
|
||||
router.patch(
|
||||
'/:id',
|
||||
requireAdmin,
|
||||
asyncHandler(async (req, res) => {
|
||||
const { status } = req.body || {};
|
||||
if (!['pending', 'approved', 'rejected'].includes(status)) {
|
||||
return res.status(400).json({ error: 'invalid_status' });
|
||||
}
|
||||
const suggestion = await suggestionsRepo.updateStatus(req.params.id, status);
|
||||
if (!suggestion) return res.status(404).json({ error: 'not_found' });
|
||||
res.json(suggestion);
|
||||
})
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:id',
|
||||
requireAdmin,
|
||||
asyncHandler(async (req, res) => {
|
||||
await suggestionsRepo.remove(req.params.id);
|
||||
res.status(204).end();
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/:id/vote',
|
||||
asyncHandler(async (req, res) => {
|
||||
const { vote, comment } = req.body || {};
|
||||
if (!['approve', 'reject'].includes(vote)) {
|
||||
return res.status(400).json({ error: 'invalid_vote' });
|
||||
}
|
||||
const suggestion = await suggestionsRepo.findById(req.params.id);
|
||||
if (!suggestion) return res.status(404).json({ error: 'not_found' });
|
||||
const result = await suggestionsRepo.upsertVote(req.params.id, req.user.id, { vote, comment });
|
||||
res.status(200).json(result);
|
||||
})
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:id/vote',
|
||||
asyncHandler(async (req, res) => {
|
||||
await suggestionsRepo.removeVote(req.params.id, req.user.id);
|
||||
res.status(204).end();
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/:id/promote',
|
||||
requireAdmin,
|
||||
asyncHandler(async (req, res) => {
|
||||
const song = await suggestionsRepo.promoteToSong(req.params.id, req.user.id);
|
||||
if (!song) return res.status(404).json({ error: 'not_found' });
|
||||
res.status(201).json(song);
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
14
src/routes/users.js
Normal file
14
src/routes/users.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
const express = require('express');
|
||||
const asyncHandler = require('../lib/asyncHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get(
|
||||
'/me',
|
||||
asyncHandler(async (req, res) => {
|
||||
const { id, name, email, is_admin } = req.user;
|
||||
res.json({ id, name, email, isAdmin: is_admin });
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
Loading…
Add table
Add a link
Reference in a new issue