mirror of
https://github.com/nfonteyne/octane-website.git
synced 2026-09-03 23:24:48 +02:00
update: visual update, phone responsive, db migration
This commit is contained in:
parent
73ebd6ad49
commit
e97aa2c6c3
16 changed files with 894 additions and 244 deletions
3
src/db/migrations/003_song_links_and_suggestion_note.sql
Normal file
3
src/db/migrations/003_song_links_and_suggestion_note.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE songs ADD COLUMN IF NOT EXISTS youtube_url TEXT;
|
||||
ALTER TABLE songs ADD COLUMN IF NOT EXISTS spotify_url TEXT;
|
||||
ALTER TABLE suggestions ADD COLUMN IF NOT EXISTS description TEXT;
|
||||
10
src/lib/spotify.js
Normal file
10
src/lib/spotify.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
function isValidSpotifyUrl(url) {
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
return parsed.hostname === 'open.spotify.com';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { isValidSpotifyUrl };
|
||||
|
|
@ -2,7 +2,7 @@ const pool = require('../db/pool');
|
|||
|
||||
async function findAll() {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT s.id, s.title, s.artist, s.notes, s.created_at,
|
||||
SELECT s.id, s.title, s.artist, s.notes, s.youtube_url, s.spotify_url, s.created_at,
|
||||
COUNT(st.id)::int AS tutorial_count
|
||||
FROM songs s
|
||||
LEFT JOIN song_tutorials st ON st.song_id = s.id
|
||||
|
|
@ -14,28 +14,29 @@ async function findAll() {
|
|||
|
||||
async function findById(id) {
|
||||
const { rows } = await pool.query(
|
||||
'SELECT id, title, artist, notes, added_by, created_at, updated_at FROM songs WHERE id = $1',
|
||||
`SELECT id, title, artist, notes, youtube_url, spotify_url, added_by, created_at, updated_at
|
||||
FROM songs WHERE id = $1`,
|
||||
[id]
|
||||
);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
async function create({ title, artist, notes, addedBy }) {
|
||||
async function create({ title, artist, notes, youtubeUrl, spotifyUrl, addedBy }) {
|
||||
const { rows } = await pool.query(
|
||||
`INSERT INTO songs (title, artist, notes, added_by)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id, title, artist, notes, added_by, created_at, updated_at`,
|
||||
[title, artist, notes || null, addedBy]
|
||||
`INSERT INTO songs (title, artist, notes, youtube_url, spotify_url, added_by)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING id, title, artist, notes, youtube_url, spotify_url, added_by, created_at, updated_at`,
|
||||
[title, artist, notes || null, youtubeUrl || null, spotifyUrl || null, addedBy]
|
||||
);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
async function update(id, { title, artist, notes }) {
|
||||
async function update(id, { title, artist, notes, youtubeUrl, spotifyUrl }) {
|
||||
const { rows } = await pool.query(
|
||||
`UPDATE songs SET title = $2, artist = $3, notes = $4, updated_at = now()
|
||||
`UPDATE songs SET title = $2, artist = $3, notes = $4, youtube_url = $5, spotify_url = $6, updated_at = now()
|
||||
WHERE id = $1
|
||||
RETURNING id, title, artist, notes, added_by, created_at, updated_at`,
|
||||
[id, title, artist, notes || null]
|
||||
RETURNING id, title, artist, notes, youtube_url, spotify_url, added_by, created_at, updated_at`,
|
||||
[id, title, artist, notes || null, youtubeUrl || null, spotifyUrl || null]
|
||||
);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ const pool = require('../db/pool');
|
|||
|
||||
async function findAll() {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT sg.id, sg.title, sg.artist, sg.youtube_url, sg.status, sg.promoted_song_id,
|
||||
SELECT sg.id, sg.title, sg.artist, sg.youtube_url, sg.description, sg.status, sg.promoted_song_id,
|
||||
sg.created_at, sg.suggested_by, u.name AS suggested_by_name,
|
||||
COUNT(*) FILTER (WHERE v.vote = 'approve')::int AS approve_count,
|
||||
COUNT(*) FILTER (WHERE v.vote = 'reject')::int AS reject_count
|
||||
|
|
@ -17,7 +17,7 @@ async function findAll() {
|
|||
|
||||
async function findById(id) {
|
||||
const { rows } = await pool.query(
|
||||
`SELECT sg.id, sg.title, sg.artist, sg.youtube_url, sg.status, sg.promoted_song_id,
|
||||
`SELECT sg.id, sg.title, sg.artist, sg.youtube_url, sg.description, sg.status, sg.promoted_song_id,
|
||||
sg.created_at, sg.suggested_by, u.name AS suggested_by_name
|
||||
FROM suggestions sg
|
||||
JOIN users u ON u.id = sg.suggested_by
|
||||
|
|
@ -39,12 +39,12 @@ async function findVotes(suggestionId) {
|
|||
return rows;
|
||||
}
|
||||
|
||||
async function create({ title, artist, youtubeUrl, suggestedBy }) {
|
||||
async function create({ title, artist, youtubeUrl, description, suggestedBy }) {
|
||||
const { rows } = await pool.query(
|
||||
`INSERT INTO suggestions (title, artist, youtube_url, suggested_by)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id, title, artist, youtube_url, status, promoted_song_id, created_at, suggested_by`,
|
||||
[title, artist || null, youtubeUrl, suggestedBy]
|
||||
`INSERT INTO suggestions (title, artist, youtube_url, description, suggested_by)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id, title, artist, youtube_url, description, status, promoted_song_id, created_at, suggested_by`,
|
||||
[title, artist || null, youtubeUrl, description || null, suggestedBy]
|
||||
);
|
||||
return rows[0];
|
||||
}
|
||||
|
|
@ -52,7 +52,7 @@ async function create({ title, artist, youtubeUrl, suggestedBy }) {
|
|||
async function updateStatus(id, status) {
|
||||
const { rows } = await pool.query(
|
||||
`UPDATE suggestions SET status = $2, updated_at = now() WHERE id = $1
|
||||
RETURNING id, title, artist, youtube_url, status, promoted_song_id, created_at, suggested_by`,
|
||||
RETURNING id, title, artist, youtube_url, description, status, promoted_song_id, created_at, suggested_by`,
|
||||
[id, status]
|
||||
);
|
||||
return rows[0] || null;
|
||||
|
|
@ -94,10 +94,10 @@ async function promoteToSong(suggestionId, addedBy) {
|
|||
return null;
|
||||
}
|
||||
const { rows: songRows } = await client.query(
|
||||
`INSERT INTO songs (title, artist, added_by)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id, title, artist, notes, added_by, created_at, updated_at`,
|
||||
[suggestion.title, suggestion.artist || suggestion.title, addedBy]
|
||||
`INSERT INTO songs (title, artist, notes, youtube_url, added_by)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id, title, artist, notes, youtube_url, spotify_url, added_by, created_at, updated_at`,
|
||||
[suggestion.title, suggestion.artist || suggestion.title, suggestion.description, suggestion.youtube_url, addedBy]
|
||||
);
|
||||
const song = songRows[0];
|
||||
await client.query(
|
||||
|
|
|
|||
|
|
@ -2,9 +2,17 @@ const express = require('express');
|
|||
const songsRepo = require('../repositories/songsRepo');
|
||||
const { requireAdmin } = require('../auth/middleware');
|
||||
const asyncHandler = require('../lib/asyncHandler');
|
||||
const { isValidYoutubeUrl } = require('../lib/youtube');
|
||||
const { isValidSpotifyUrl } = require('../lib/spotify');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
function validateLinks(youtubeUrl, spotifyUrl) {
|
||||
if (youtubeUrl && !isValidYoutubeUrl(youtubeUrl)) return 'invalid_youtube_url';
|
||||
if (spotifyUrl && !isValidSpotifyUrl(spotifyUrl)) return 'invalid_spotify_url';
|
||||
return null;
|
||||
}
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req, res) => {
|
||||
|
|
@ -26,11 +34,20 @@ router.post(
|
|||
'/',
|
||||
requireAdmin,
|
||||
asyncHandler(async (req, res) => {
|
||||
const { title, artist, notes } = req.body || {};
|
||||
const { title, artist, notes, youtubeUrl, spotifyUrl } = 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 });
|
||||
const linkError = validateLinks(youtubeUrl, spotifyUrl);
|
||||
if (linkError) return res.status(400).json({ error: linkError });
|
||||
const song = await songsRepo.create({
|
||||
title: title.trim(),
|
||||
artist: artist.trim(),
|
||||
notes,
|
||||
youtubeUrl: youtubeUrl ? youtubeUrl.trim() : null,
|
||||
spotifyUrl: spotifyUrl ? spotifyUrl.trim() : null,
|
||||
addedBy: req.user.id,
|
||||
});
|
||||
res.status(201).json(song);
|
||||
})
|
||||
);
|
||||
|
|
@ -39,11 +56,19 @@ router.patch(
|
|||
'/:id',
|
||||
requireAdmin,
|
||||
asyncHandler(async (req, res) => {
|
||||
const { title, artist, notes } = req.body || {};
|
||||
const { title, artist, notes, youtubeUrl, spotifyUrl } = 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 });
|
||||
const linkError = validateLinks(youtubeUrl, spotifyUrl);
|
||||
if (linkError) return res.status(400).json({ error: linkError });
|
||||
const song = await songsRepo.update(req.params.id, {
|
||||
title: title.trim(),
|
||||
artist: artist.trim(),
|
||||
notes,
|
||||
youtubeUrl: youtubeUrl ? youtubeUrl.trim() : null,
|
||||
spotifyUrl: spotifyUrl ? spotifyUrl.trim() : null,
|
||||
});
|
||||
if (!song) return res.status(404).json({ error: 'not_found' });
|
||||
res.json(song);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ router.get(
|
|||
router.post(
|
||||
'/',
|
||||
asyncHandler(async (req, res) => {
|
||||
const { title, artist, youtubeUrl } = req.body || {};
|
||||
const { title, artist, youtubeUrl, description } = req.body || {};
|
||||
if (!title || !title.trim() || !youtubeUrl || !youtubeUrl.trim()) {
|
||||
return res.status(400).json({ error: 'title_and_youtube_url_required' });
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@ router.post(
|
|||
title: title.trim(),
|
||||
artist: artist ? artist.trim() : null,
|
||||
youtubeUrl: youtubeUrl.trim(),
|
||||
description: description ? description.trim() : null,
|
||||
suggestedBy: req.user.id,
|
||||
});
|
||||
res.status(201).json(suggestion);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue