mirror of
https://github.com/nfonteyne/octane-website.git
synced 2026-09-03 23:24:48 +02:00
fix: cant add multiple future rehersals
This commit is contained in:
parent
749abda8ba
commit
5e28345ed5
7 changed files with 196 additions and 35 deletions
|
|
@ -274,7 +274,7 @@ erDiagram
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `/index.html` | Tous (lecture et écriture) | Répertoire des morceaux travaillés, avec recherche instantanée (titre/artiste), liens/vignettes YouTube et Spotify, tutos embarqués par morceau et par instrument. Ajout avec autocomplete titre/artiste + liens auto-trouvés ([détails](#recherche-automatique-de-morceaux)) |
|
| `/index.html` | Tous (lecture et écriture) | Répertoire des morceaux travaillés, avec recherche instantanée (titre/artiste), liens/vignettes YouTube et Spotify, tutos embarqués par morceau et par instrument. Ajout avec autocomplete titre/artiste + liens auto-trouvés ([détails](#recherche-automatique-de-morceaux)) |
|
||||||
| `/suggestions.html` | Tous | Proposer un morceau (liens YouTube et Spotify + note libre), voter approuver/rejeter avec commentaire (attribué nominativement), ajouter une suggestion au répertoire |
|
| `/suggestions.html` | Tous | Proposer un morceau (liens YouTube et Spotify + note libre), voter approuver/rejeter avec commentaire (attribué nominativement), ajouter une suggestion au répertoire |
|
||||||
| `/concerts.html` | Tous (lecture et écriture) | Deux onglets sur une même page. **Prochain concert** : choix des morceaux du répertoire, ordre, notes, section rappel, lien "Écouter la setlist sur YouTube". **Historique** : concerts passés, modifiables (date, morceaux, ordre, rappel) et supprimables, avec le même lien playlist ; deux vues, chronologique (par défaut, tous les concerts détaillés avec YouTube embarqué par morceau, du plus récent au plus ancien) et réduite (liste compacte) ; ouvrir un concert passé (`?tab=history&id=...`) reste partageable en lien direct |
|
| `/concerts.html` | Tous (lecture et écriture) | Deux onglets sur une même page. **Prochain concert** : liste de tous les concerts à venir (pas seulement le plus proche), créer autant de concerts à venir que nécessaire ; chaque ligne a des actions Modifier/Supprimer directement visibles à droite (icône seule sur mobile) ; ouvrir un concert donne accès au choix des morceaux du répertoire, ordre, notes, section rappel, lien "Écouter la setlist sur YouTube". **Historique** : concerts passés, modifiables (date, morceaux, ordre, rappel) et supprimables, avec le même lien playlist ; deux vues, chronologique (par défaut, tous les concerts détaillés avec YouTube embarqué par morceau, du plus récent au plus ancien) et réduite (liste compacte) ; ouvrir un concert passé (`?tab=history&id=...`) reste partageable en lien direct |
|
||||||
| `/profile.html` | Chacun voit le sien | Profil issu d'Authentik (nom, avatar, groupes) + activité personnelle (morceaux ajoutés, suggestions, votes) ; nom affiché personnalisable (par défaut celui d'Authentik) ; gestion de ses calendriers ICS pour les disponibilités |
|
| `/profile.html` | Chacun voit le sien | Profil issu d'Authentik (nom, avatar, groupes) + activité personnelle (morceaux ajoutés, suggestions, votes) ; nom affiché personnalisable (par défaut celui d'Authentik) ; gestion de ses calendriers ICS pour les disponibilités |
|
||||||
| `/calendar.html` | Tous (lecture et écriture) | Disponibilités du groupe pour les 3 prochaines semaines (calendrier, filtres par personne, modale par jour) ; gestion de ses propres calendriers ICS directement depuis la page ; proposer une date de répétition et l'ajouter en un clic à son calendrier (Google/Outlook/Apple) — [détails](#disponibilités-calendrier) |
|
| `/calendar.html` | Tous (lecture et écriture) | Disponibilités du groupe pour les 3 prochaines semaines (calendrier, filtres par personne, modale par jour) ; gestion de ses propres calendriers ICS directement depuis la page ; proposer une date de répétition et l'ajouter en un clic à son calendrier (Google/Outlook/Apple) — [détails](#disponibilités-calendrier) |
|
||||||
| `/calendar-ics-help.html` | Tous | Page d'aide : qu'est-ce qu'un lien ICS, comment le trouver sur Google/Outlook/Apple, comment le révoquer/régénérer |
|
| `/calendar-ics-help.html` | Tous | Page d'aide : qu'est-ce qu'un lien ICS, comment le trouver sur Google/Outlook/Apple, comment le révoquer/régénérer |
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,26 @@
|
||||||
<div id="error"></div>
|
<div id="error"></div>
|
||||||
|
|
||||||
<div id="next-view">
|
<div id="next-view">
|
||||||
<div id="next-content">Chargement…</div>
|
<div id="next-list-view">
|
||||||
|
<div class="section-header" style="margin:0 0 0.75rem">
|
||||||
|
<p class="page-subtitle" style="margin:0">Concerts à venir.</p>
|
||||||
|
<button type="button" class="secondary" id="toggle-add-concert-btn">+ Proposer un concert</button>
|
||||||
|
</div>
|
||||||
|
<div class="panel" id="add-concert-panel" style="display:none">
|
||||||
|
<form id="add-concert-form" class="stacked-form">
|
||||||
|
<label>Nom <input name="name"></label>
|
||||||
|
<label>Lieu <input name="venue"></label>
|
||||||
|
<label>Date <input type="date" name="concertDate" required></label>
|
||||||
|
<button type="submit">Créer le concert</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="upcoming-concerts-list"><p class="empty">Chargement…</p></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="next-detail-view" style="display:none">
|
||||||
|
<a href="#" class="back-link" id="back-to-upcoming-link">← Retour aux concerts à venir</a>
|
||||||
|
<div id="next-detail-content"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="history-view" style="display:none">
|
<div id="history-view" style="display:none">
|
||||||
|
|
|
||||||
|
|
@ -1440,3 +1440,37 @@ a.back-link:hover { color: var(--accent); }
|
||||||
.cal-cell.has-rehearsal.has-concert {
|
.cal-cell.has-rehearsal.has-concert {
|
||||||
background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 20%, var(--surface)), color-mix(in srgb, var(--accent-2) 20%, var(--surface)));
|
background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 20%, var(--surface)), color-mix(in srgb, var(--accent-2) 20%, var(--surface)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- Concerts: upcoming list + detail actions ---------- */
|
||||||
|
|
||||||
|
.concert-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.75rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.concert-row-info { cursor: pointer; flex: 1 1 auto; }
|
||||||
|
|
||||||
|
.concert-row-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.concert-detail-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.75rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon-only on narrow screens — the emoji glyph alone is enough to convey
|
||||||
|
"modifier"/"supprimer" once space is tight, matching how the nav collapses
|
||||||
|
the same way at this breakpoint. */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.btn-label { display: none; }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,105 @@ function bindConcertLinks(container) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- Prochain concert : liste des concerts à venir ----------
|
||||||
|
|
||||||
|
let upcomingConcerts = [];
|
||||||
|
|
||||||
|
function upcomingConcertRowTemplate(c) {
|
||||||
|
return `
|
||||||
|
<div class="card concert-row" data-concert-id="${c.id}">
|
||||||
|
<div class="concert-row-info" data-concert-id="${c.id}">
|
||||||
|
<div class="card-title">${escapeHtml(c.name || 'Concert')}</div>
|
||||||
|
<div class="card-subtitle">${escapeHtml(c.venue || '')} · ${formatDateShort(c.concert_date)}</div>
|
||||||
|
</div>
|
||||||
|
<div class="concert-row-actions">
|
||||||
|
<button type="button" class="secondary icon-btn edit-concert-btn" data-concert-id="${c.id}" title="Modifier">✏️<span class="btn-label"> Modifier</span></button>
|
||||||
|
<button type="button" class="danger icon-btn delete-concert-btn" data-concert-id="${c.id}" title="Supprimer">🗑️<span class="btn-label"> Supprimer</span></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadUpcoming() {
|
||||||
|
upcomingConcerts = await api.get('/api/setlists/upcoming');
|
||||||
|
renderUpcomingList();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderUpcomingList() {
|
||||||
|
const container = document.getElementById('upcoming-concerts-list');
|
||||||
|
container.innerHTML = upcomingConcerts.length
|
||||||
|
? upcomingConcerts.map(upcomingConcertRowTemplate).join('')
|
||||||
|
: '<p class="empty">Aucun concert à venir pour le moment.</p>';
|
||||||
|
container.querySelectorAll('.concert-row-info').forEach((el) => {
|
||||||
|
el.addEventListener('click', () => showNextDetail(el.dataset.concertId));
|
||||||
|
});
|
||||||
|
container.querySelectorAll('.edit-concert-btn').forEach((btn) => {
|
||||||
|
btn.addEventListener('click', () => showNextDetail(btn.dataset.concertId));
|
||||||
|
});
|
||||||
|
container.querySelectorAll('.delete-concert-btn').forEach((btn) => {
|
||||||
|
btn.addEventListener('click', () => onDeleteUpcoming(btn.dataset.concertId));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onDeleteUpcoming(id) {
|
||||||
|
try {
|
||||||
|
await api.del(`/api/setlists/${id}`);
|
||||||
|
await loadUpcoming();
|
||||||
|
} catch (err) {
|
||||||
|
showError(err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onAddConcert(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const form = e.target;
|
||||||
|
const name = form.name.value.trim();
|
||||||
|
const venue = form.venue.value.trim();
|
||||||
|
const concertDate = form.concertDate.value;
|
||||||
|
const submitBtn = form.querySelector('button[type="submit"]');
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
try {
|
||||||
|
await api.post('/api/setlists', { name, venue, concertDate });
|
||||||
|
form.reset();
|
||||||
|
document.getElementById('add-concert-panel').style.display = 'none';
|
||||||
|
document.getElementById('toggle-add-concert-btn').textContent = '+ Proposer un concert';
|
||||||
|
await loadUpcoming();
|
||||||
|
} catch (err) {
|
||||||
|
showError(err.message);
|
||||||
|
} finally {
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showUpcomingList() {
|
||||||
|
document.getElementById('next-detail-view').style.display = 'none';
|
||||||
|
document.getElementById('next-list-view').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function showNextDetail(id) {
|
||||||
|
document.getElementById('next-list-view').style.display = 'none';
|
||||||
|
document.getElementById('next-detail-view').style.display = 'block';
|
||||||
|
|
||||||
|
const editor = createSetlistEditor({
|
||||||
|
containerId: 'next-detail-content',
|
||||||
|
allSongs,
|
||||||
|
getSetlist: async () => {
|
||||||
|
try {
|
||||||
|
return await api.get(`/api/setlists/${id}`);
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emptyMessage: 'Concert introuvable.',
|
||||||
|
allowDelete: true,
|
||||||
|
onDeleted: async () => {
|
||||||
|
showUpcomingList();
|
||||||
|
await loadUpcoming();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
editor.load();
|
||||||
|
}
|
||||||
|
|
||||||
function renderTimeline() {
|
function renderTimeline() {
|
||||||
const container = document.getElementById('timeline-view');
|
const container = document.getElementById('timeline-view');
|
||||||
if (!concerts.length) {
|
if (!concerts.length) {
|
||||||
|
|
@ -168,16 +267,6 @@ function showHistoryDetail(id) {
|
||||||
await initNav('concerts');
|
await initNav('concerts');
|
||||||
allSongs = await api.get('/api/songs');
|
allSongs = await api.get('/api/songs');
|
||||||
|
|
||||||
const nextEditor = createSetlistEditor({
|
|
||||||
containerId: 'next-content',
|
|
||||||
allSongs,
|
|
||||||
getSetlist: () => api.get('/api/setlists/next'),
|
|
||||||
createSetlist: (data) => api.post('/api/setlists', data),
|
|
||||||
emptyMessage: 'Aucun concert à venir pour le moment.',
|
|
||||||
allowDelete: true,
|
|
||||||
onDeleted: () => nextEditor.load(),
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('tab-next').addEventListener('click', () => switchTab('next'));
|
document.getElementById('tab-next').addEventListener('click', () => switchTab('next'));
|
||||||
document.getElementById('tab-history').addEventListener('click', () => switchTab('history'));
|
document.getElementById('tab-history').addEventListener('click', () => switchTab('history'));
|
||||||
document.getElementById('toggle-history-mode-btn').addEventListener('click', () => {
|
document.getElementById('toggle-history-mode-btn').addEventListener('click', () => {
|
||||||
|
|
@ -188,9 +277,20 @@ function showHistoryDetail(id) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
showHistoryList();
|
showHistoryList();
|
||||||
});
|
});
|
||||||
|
document.getElementById('back-to-upcoming-link').addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
showUpcomingList();
|
||||||
|
});
|
||||||
|
document.getElementById('toggle-add-concert-btn').addEventListener('click', () => {
|
||||||
|
const panel = document.getElementById('add-concert-panel');
|
||||||
|
const isOpen = panel.style.display !== 'none';
|
||||||
|
panel.style.display = isOpen ? 'none' : 'block';
|
||||||
|
document.getElementById('toggle-add-concert-btn').textContent = isOpen ? '+ Proposer un concert' : 'Annuler';
|
||||||
|
});
|
||||||
|
document.getElementById('add-concert-form').addEventListener('submit', onAddConcert);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await nextEditor.load();
|
await loadUpcoming();
|
||||||
await loadHistory();
|
await loadHistory();
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ function createSetlistEditor({
|
||||||
let setlist = null;
|
let setlist = null;
|
||||||
let mainRows = [];
|
let mainRows = [];
|
||||||
let encoreRows = [];
|
let encoreRows = [];
|
||||||
let actionsRevealed = false;
|
|
||||||
|
|
||||||
function formatDate(dateStr) {
|
function formatDate(dateStr) {
|
||||||
const d = new Date(dateStr);
|
const d = new Date(dateStr);
|
||||||
|
|
@ -35,11 +34,16 @@ function createSetlistEditor({
|
||||||
const rowHtml = (s) => `<li><span class="row-title">${escapeHtml(s.title)}</span> <span class="row-artist">— ${escapeHtml(s.artist)}</span>${s.note ? `<span class="note">${escapeHtml(s.note)}</span>` : ''}</li>`;
|
const rowHtml = (s) => `<li><span class="row-title">${escapeHtml(s.title)}</span> <span class="row-artist">— ${escapeHtml(s.artist)}</span>${s.note ? `<span class="note">${escapeHtml(s.note)}</span>` : ''}</li>`;
|
||||||
const playlistUrl = youtubePlaylistUrl([...main, ...encore].map((s) => s.youtube_url).filter(Boolean));
|
const playlistUrl = youtubePlaylistUrl([...main, ...encore].map((s) => s.youtube_url).filter(Boolean));
|
||||||
return `
|
return `
|
||||||
<div class="card" id="concert-summary-card" style="cursor:pointer">
|
<div class="card concert-detail-card">
|
||||||
|
<div>
|
||||||
<div class="card-title">${escapeHtml(setlist.name || 'Concert')}</div>
|
<div class="card-title">${escapeHtml(setlist.name || 'Concert')}</div>
|
||||||
<div class="card-subtitle">${escapeHtml(setlist.venue || '')} · ${formatDate(setlist.concert_date)}</div>
|
<div class="card-subtitle">${escapeHtml(setlist.venue || '')} · ${formatDate(setlist.concert_date)}</div>
|
||||||
${playlistUrl ? `<div class="song-links"><a class="pill-link youtube" id="playlist-link" href="${playlistUrl}" target="_blank" rel="noopener">► Écouter la setlist sur YouTube</a></div>` : ''}
|
${playlistUrl ? `<div class="song-links"><a class="pill-link youtube" href="${playlistUrl}" target="_blank" rel="noopener">► Écouter la setlist sur YouTube</a></div>` : ''}
|
||||||
<p class="note" style="margin-bottom:0">${actionsRevealed ? 'Cliquer pour masquer les actions' : 'Cliquer pour modifier ou supprimer'}</p>
|
</div>
|
||||||
|
<div class="concert-row-actions">
|
||||||
|
<button id="edit-btn" type="button" class="secondary icon-btn" title="Modifier">✏️<span class="btn-label"> Modifier</span></button>
|
||||||
|
${allowDelete ? '<button id="delete-btn" type="button" class="danger icon-btn" title="Supprimer">🗑️<span class="btn-label"> Supprimer</span></button>' : ''}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="setlist-section">
|
<div class="setlist-section">
|
||||||
<h3>Setlist</h3>
|
<h3>Setlist</h3>
|
||||||
|
|
@ -49,11 +53,6 @@ function createSetlistEditor({
|
||||||
<h3>Rappel</h3>
|
<h3>Rappel</h3>
|
||||||
${encore.length ? `<ol class="setlist">${encore.map(rowHtml).join('')}</ol>` : '<p class="empty">Aucun morceau de rappel prévu.</p>'}
|
${encore.length ? `<ol class="setlist">${encore.map(rowHtml).join('')}</ol>` : '<p class="empty">Aucun morceau de rappel prévu.</p>'}
|
||||||
</div>
|
</div>
|
||||||
${actionsRevealed ? `
|
|
||||||
<div class="inline-form" style="margin-top:1rem">
|
|
||||||
<button id="edit-btn" class="secondary">Modifier</button>
|
|
||||||
${allowDelete ? '<button id="delete-btn" class="danger">Supprimer ce concert</button>' : ''}
|
|
||||||
</div>` : ''}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,16 +65,9 @@ function createSetlistEditor({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
container().innerHTML = readOnlyView();
|
container().innerHTML = readOnlyView();
|
||||||
document.getElementById('concert-summary-card').addEventListener('click', () => {
|
document.getElementById('edit-btn').addEventListener('click', enterEditMode);
|
||||||
actionsRevealed = !actionsRevealed;
|
|
||||||
renderReadOnly();
|
|
||||||
});
|
|
||||||
const playlistLink = document.getElementById('playlist-link');
|
|
||||||
if (playlistLink) playlistLink.addEventListener('click', (e) => e.stopPropagation());
|
|
||||||
const editBtn = document.getElementById('edit-btn');
|
|
||||||
if (editBtn) editBtn.addEventListener('click', (e) => { e.stopPropagation(); enterEditMode(); });
|
|
||||||
const deleteBtn = document.getElementById('delete-btn');
|
const deleteBtn = document.getElementById('delete-btn');
|
||||||
if (deleteBtn) deleteBtn.addEventListener('click', (e) => { e.stopPropagation(); onDelete(); });
|
if (deleteBtn) deleteBtn.addEventListener('click', onDelete);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onDelete() {
|
async function onDelete() {
|
||||||
|
|
@ -168,7 +160,6 @@ function createSetlistEditor({
|
||||||
}
|
}
|
||||||
|
|
||||||
function enterEditMode() {
|
function enterEditMode() {
|
||||||
actionsRevealed = false;
|
|
||||||
mainRows = setlist.songs
|
mainRows = setlist.songs
|
||||||
.filter((s) => !s.is_encore)
|
.filter((s) => !s.is_encore)
|
||||||
.map((s) => ({ setlistSongId: s.id, songId: s.song_id, title: s.title, artist: s.artist, note: s.note }));
|
.map((s) => ({ setlistSongId: s.id, songId: s.song_id, title: s.title, artist: s.artist, note: s.note }));
|
||||||
|
|
@ -285,7 +276,6 @@ function createSetlistEditor({
|
||||||
async load() {
|
async load() {
|
||||||
try {
|
try {
|
||||||
setlist = await getSetlist();
|
setlist = await getSetlist();
|
||||||
actionsRevealed = false;
|
|
||||||
renderReadOnly();
|
renderReadOnly();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showError(err.message);
|
showError(err.message);
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,16 @@ async function findNext() {
|
||||||
return rows[0] || null;
|
return rows[0] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function findUpcoming() {
|
||||||
|
const { rows } = await pool.query(
|
||||||
|
`SELECT id, name, venue, concert_date, created_by, created_at, updated_at
|
||||||
|
FROM setlists
|
||||||
|
WHERE concert_date >= CURRENT_DATE
|
||||||
|
ORDER BY concert_date ASC`
|
||||||
|
);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
async function findHistory() {
|
async function findHistory() {
|
||||||
const { rows } = await pool.query(
|
const { rows } = await pool.query(
|
||||||
`SELECT id, name, venue, concert_date, created_by, created_at, updated_at
|
`SELECT id, name, venue, concert_date, created_by, created_at, updated_at
|
||||||
|
|
@ -152,6 +162,7 @@ async function getStats() {
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
findNext,
|
findNext,
|
||||||
|
findUpcoming,
|
||||||
findHistory,
|
findHistory,
|
||||||
findHistoryWithSongs,
|
findHistoryWithSongs,
|
||||||
findById,
|
findById,
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,13 @@ router.get(
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/upcoming',
|
||||||
|
asyncHandler(async (req, res) => {
|
||||||
|
res.json(await setlistsRepo.findUpcoming());
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/history',
|
'/history',
|
||||||
asyncHandler(async (req, res) => {
|
asyncHandler(async (req, res) => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue