chore: simplify playlist visibility logic and update dependencies
- Remove `VISIBILITY_RANK` and auto-upgrade logic from admin routes - Simplify `playlists.js` by removing `getEffectiveVisibility` and complex `NOT EXISTS` subqueries in list queries - Update `public.js` to use direct playlist visibility instead of effective visibility - Bump `better-sqlite3` to v11.10.0 and set project version to 1.0.0
This commit is contained in:
parent
6be01104cb
commit
bb6a1a962e
|
|
@ -1,15 +1,15 @@
|
||||||
{
|
{
|
||||||
"name": "tunes",
|
"name": "tunes",
|
||||||
"version": "0.1.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "tunes",
|
"name": "tunes",
|
||||||
"version": "0.1.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argon2": "^0.40.1",
|
"argon2": "^0.40.1",
|
||||||
"better-sqlite3": "^11.3.0",
|
"better-sqlite3": "^11.10.0",
|
||||||
"better-sqlite3-session-store": "^0.1.0",
|
"better-sqlite3-session-store": "^0.1.0",
|
||||||
"cropperjs": "^1.6.2",
|
"cropperjs": "^1.6.2",
|
||||||
"csrf-sync": "^4.0.3",
|
"csrf-sync": "^4.0.3",
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argon2": "^0.40.1",
|
"argon2": "^0.40.1",
|
||||||
"better-sqlite3": "^11.3.0",
|
"better-sqlite3": "^11.10.0",
|
||||||
"better-sqlite3-session-store": "^0.1.0",
|
"better-sqlite3-session-store": "^0.1.0",
|
||||||
"cropperjs": "^1.6.2",
|
"cropperjs": "^1.6.2",
|
||||||
"csrf-sync": "^4.0.3",
|
"csrf-sync": "^4.0.3",
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ const songFields = upload.fields([{ name: 'audio', maxCount: 1 }, { name: 'cover
|
||||||
const playlistFields = upload.fields([{ name: 'cover', maxCount: 1 }]);
|
const playlistFields = upload.fields([{ name: 'cover', maxCount: 1 }]);
|
||||||
|
|
||||||
const VISIBILITY_VALUES = ['public', 'logged_in', 'vip', 'private'];
|
const VISIBILITY_VALUES = ['public', 'logged_in', 'vip', 'private'];
|
||||||
const VISIBILITY_RANK = { public: 0, logged_in: 1, vip: 2, private: 3 };
|
|
||||||
|
|
||||||
// For uploads, all text fields are optional — we'll fill blanks from ID3 tags.
|
// For uploads, all text fields are optional — we'll fill blanks from ID3 tags.
|
||||||
// The edit form enforces title/artist via a stricter schema below.
|
// The edit form enforces title/artist via a stricter schema below.
|
||||||
|
|
@ -204,9 +203,6 @@ module.exports = function adminRoutes(csrfProtection) {
|
||||||
const song = songs.findById(songId);
|
const song = songs.findById(songId);
|
||||||
if (!song) continue;
|
if (!song) continue;
|
||||||
playlists.addTrack(playlistId, songId);
|
playlists.addTrack(playlistId, songId);
|
||||||
if (VISIBILITY_RANK[song.visibility] > VISIBILITY_RANK[playlist.visibility]) {
|
|
||||||
playlists.setVisibility(playlistId, song.visibility);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
res.json({ ok: true, added: songIds.length });
|
res.json({ ok: true, added: songIds.length });
|
||||||
} catch (err) { next(err); }
|
} catch (err) { next(err); }
|
||||||
|
|
@ -417,9 +413,6 @@ module.exports = function adminRoutes(csrfProtection) {
|
||||||
const song = songs.findById(songId);
|
const song = songs.findById(songId);
|
||||||
if (!song) continue;
|
if (!song) continue;
|
||||||
playlists.addTrack(created.id, songId);
|
playlists.addTrack(created.id, songId);
|
||||||
if (VISIBILITY_RANK[song.visibility] > VISIBILITY_RANK[created.visibility]) {
|
|
||||||
playlists.setVisibility(created.id, song.visibility);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
playlists.updateTopGenres(created.id);
|
playlists.updateTopGenres(created.id);
|
||||||
req.flash('success', 'Playlist created.');
|
req.flash('success', 'Playlist created.');
|
||||||
|
|
@ -498,10 +491,6 @@ module.exports = function adminRoutes(csrfProtection) {
|
||||||
if (!playlist || !song) return next();
|
if (!playlist || !song) return next();
|
||||||
if (!canManage(playlist, req.session.user)) return forbidden(next);
|
if (!canManage(playlist, req.session.user)) return forbidden(next);
|
||||||
playlists.addTrack(id, songId);
|
playlists.addTrack(id, songId);
|
||||||
// Auto-upgrade playlist visibility if the song is more restrictive.
|
|
||||||
if (VISIBILITY_RANK[song.visibility] > VISIBILITY_RANK[playlist.visibility]) {
|
|
||||||
playlists.setVisibility(id, song.visibility);
|
|
||||||
}
|
|
||||||
playlists.updateTopGenres(id);
|
playlists.updateTopGenres(id);
|
||||||
res.redirect(`/mymusic/playlists/${id}/edit`);
|
res.redirect(`/mymusic/playlists/${id}/edit`);
|
||||||
} catch (err) { next(err); }
|
} catch (err) { next(err); }
|
||||||
|
|
|
||||||
|
|
@ -120,10 +120,8 @@ router.get('/playlists/:slug', (req, res, next) => {
|
||||||
if (!playlist) return next();
|
if (!playlist) return next();
|
||||||
const userId = req.session.user?.id || null;
|
const userId = req.session.user?.id || null;
|
||||||
const isVip = !!(req.session.user?.isVip || req.session.user?.role === 'admin');
|
const isVip = !!(req.session.user?.isVip || req.session.user?.role === 'admin');
|
||||||
// Use effective visibility (most restrictive of playlist + all its tracks).
|
if (!canView(playlist.visibility, playlist.createdBy, userId, isVip)) {
|
||||||
const effectiveVisibility = playlists.getEffectiveVisibility(playlist.id);
|
return denyAccess(req, res, next, playlist.visibility);
|
||||||
if (!canView(effectiveVisibility, playlist.createdBy, userId, isVip)) {
|
|
||||||
return denyAccess(req, res, next, effectiveVisibility);
|
|
||||||
}
|
}
|
||||||
social.enrichPlaylists([playlist], userId);
|
social.enrichPlaylists([playlist], userId);
|
||||||
const tracks = playlists.getTracks(playlist.id).filter(t => canView(t.visibility, t.uploadedBy, userId, isVip) && (!t.isNsfw || !!userId));
|
const tracks = playlists.getTracks(playlist.id).filter(t => canView(t.visibility, t.uploadedBy, userId, isVip) && (!t.isNsfw || !!userId));
|
||||||
|
|
|
||||||
|
|
@ -14,58 +14,29 @@ const setTopGenresStmt = db.prepare('UPDATE playlists SET top_genres = ? WHERE i
|
||||||
const deleteStmt = db.prepare('DELETE FROM playlists WHERE id = ?');
|
const deleteStmt = db.prepare('DELETE FROM playlists WHERE id = ?');
|
||||||
const findByIdStmt = db.prepare('SELECT * FROM playlists WHERE id = ?');
|
const findByIdStmt = db.prepare('SELECT * FROM playlists WHERE id = ?');
|
||||||
|
|
||||||
// For list queries, effective visibility = max(playlist.visibility, most restrictive track).
|
|
||||||
// Guests only see playlists where nothing in them is logged_in, vip, or private.
|
|
||||||
const listGuestStmt = db.prepare(`
|
const listGuestStmt = db.prepare(`
|
||||||
SELECT p.* FROM playlists p
|
SELECT p.* FROM playlists p
|
||||||
WHERE p.visibility = 'public'
|
WHERE p.visibility = 'public'
|
||||||
AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM playlist_songs ps
|
|
||||||
JOIN songs s ON s.id = ps.song_id
|
|
||||||
WHERE ps.playlist_id = p.id AND s.visibility IN ('logged_in', 'vip', 'private')
|
|
||||||
)
|
|
||||||
ORDER BY p.created_at DESC
|
ORDER BY p.created_at DESC
|
||||||
`);
|
`);
|
||||||
// Logged-in non-VIP users see public/logged_in playlists where nothing in them is vip or private.
|
|
||||||
const listUserStmt = db.prepare(`
|
const listUserStmt = db.prepare(`
|
||||||
SELECT p.* FROM playlists p
|
SELECT p.* FROM playlists p
|
||||||
WHERE p.visibility IN ('public', 'logged_in')
|
WHERE p.visibility IN ('public', 'logged_in')
|
||||||
AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM playlist_songs ps
|
|
||||||
JOIN songs s ON s.id = ps.song_id
|
|
||||||
WHERE ps.playlist_id = p.id AND s.visibility IN ('vip', 'private')
|
|
||||||
)
|
|
||||||
ORDER BY p.created_at DESC
|
ORDER BY p.created_at DESC
|
||||||
`);
|
`);
|
||||||
// VIP users: public + logged_in + vip playlists (no private tracks)
|
|
||||||
const listVipStmt = db.prepare(`
|
const listVipStmt = db.prepare(`
|
||||||
SELECT p.* FROM playlists p
|
SELECT p.* FROM playlists p
|
||||||
WHERE p.visibility IN ('public', 'logged_in', 'vip')
|
WHERE p.visibility IN ('public', 'logged_in', 'vip')
|
||||||
AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM playlist_songs ps
|
|
||||||
JOIN songs s ON s.id = ps.song_id
|
|
||||||
WHERE ps.playlist_id = p.id AND s.visibility = 'private'
|
|
||||||
)
|
|
||||||
ORDER BY p.created_at DESC
|
ORDER BY p.created_at DESC
|
||||||
`);
|
`);
|
||||||
const featuredVipStmt = db.prepare(`
|
const featuredVipStmt = db.prepare(`
|
||||||
SELECT p.* FROM playlists p
|
SELECT p.* FROM playlists p
|
||||||
WHERE p.visibility IN ('public', 'logged_in', 'vip')
|
WHERE p.visibility IN ('public', 'logged_in', 'vip')
|
||||||
AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM playlist_songs ps
|
|
||||||
JOIN songs s ON s.id = ps.song_id
|
|
||||||
WHERE ps.playlist_id = p.id AND s.visibility = 'private'
|
|
||||||
)
|
|
||||||
ORDER BY RANDOM() LIMIT ?
|
ORDER BY RANDOM() LIMIT ?
|
||||||
`);
|
`);
|
||||||
const listPublicByUserVipStmt = db.prepare(`
|
const listPublicByUserVipStmt = db.prepare(`
|
||||||
SELECT p.* FROM playlists p
|
SELECT p.* FROM playlists p
|
||||||
WHERE p.created_by = ? AND p.visibility IN ('public', 'logged_in', 'vip')
|
WHERE p.created_by = ? AND p.visibility IN ('public', 'logged_in', 'vip')
|
||||||
AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM playlist_songs ps
|
|
||||||
JOIN songs s ON s.id = ps.song_id
|
|
||||||
WHERE ps.playlist_id = p.id AND s.visibility = 'private'
|
|
||||||
)
|
|
||||||
ORDER BY p.created_at DESC
|
ORDER BY p.created_at DESC
|
||||||
`);
|
`);
|
||||||
const forSongVipStmt = db.prepare(`
|
const forSongVipStmt = db.prepare(`
|
||||||
|
|
@ -80,41 +51,14 @@ const listByUserStmt = db.prepare('SELECT * FROM playlists WHERE created_by = ?
|
||||||
const featuredGuestStmt = db.prepare(`
|
const featuredGuestStmt = db.prepare(`
|
||||||
SELECT p.* FROM playlists p
|
SELECT p.* FROM playlists p
|
||||||
WHERE p.visibility = 'public'
|
WHERE p.visibility = 'public'
|
||||||
AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM playlist_songs ps
|
|
||||||
JOIN songs s ON s.id = ps.song_id
|
|
||||||
WHERE ps.playlist_id = p.id AND s.visibility IN ('logged_in', 'vip', 'private')
|
|
||||||
)
|
|
||||||
ORDER BY RANDOM() LIMIT ?
|
ORDER BY RANDOM() LIMIT ?
|
||||||
`);
|
`);
|
||||||
const featuredUserStmt = db.prepare(`
|
const featuredUserStmt = db.prepare(`
|
||||||
SELECT p.* FROM playlists p
|
SELECT p.* FROM playlists p
|
||||||
WHERE p.visibility IN ('public', 'logged_in')
|
WHERE p.visibility IN ('public', 'logged_in')
|
||||||
AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM playlist_songs ps
|
|
||||||
JOIN songs s ON s.id = ps.song_id
|
|
||||||
WHERE ps.playlist_id = p.id AND s.visibility IN ('vip', 'private')
|
|
||||||
)
|
|
||||||
ORDER BY RANDOM() LIMIT ?
|
ORDER BY RANDOM() LIMIT ?
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// Compute the single most-restrictive visibility for one playlist (stored + tracks).
|
|
||||||
const effectiveVisibilityStmt = db.prepare(`
|
|
||||||
SELECT
|
|
||||||
CASE
|
|
||||||
WHEN p.visibility = 'private'
|
|
||||||
OR EXISTS (SELECT 1 FROM playlist_songs ps JOIN songs s ON s.id = ps.song_id WHERE ps.playlist_id = p.id AND s.visibility = 'private')
|
|
||||||
THEN 'private'
|
|
||||||
WHEN p.visibility = 'vip'
|
|
||||||
OR EXISTS (SELECT 1 FROM playlist_songs ps JOIN songs s ON s.id = ps.song_id WHERE ps.playlist_id = p.id AND s.visibility = 'vip')
|
|
||||||
THEN 'vip'
|
|
||||||
WHEN p.visibility = 'logged_in'
|
|
||||||
OR EXISTS (SELECT 1 FROM playlist_songs ps JOIN songs s ON s.id = ps.song_id WHERE ps.playlist_id = p.id AND s.visibility = 'logged_in')
|
|
||||||
THEN 'logged_in'
|
|
||||||
ELSE 'public'
|
|
||||||
END AS effective_visibility
|
|
||||||
FROM playlists p WHERE p.id = ?
|
|
||||||
`);
|
|
||||||
|
|
||||||
const trackGenresStmt = db.prepare(`
|
const trackGenresStmt = db.prepare(`
|
||||||
SELECT s.genre FROM playlist_songs ps
|
SELECT s.genre FROM playlist_songs ps
|
||||||
|
|
@ -273,11 +217,6 @@ function findById(id) {
|
||||||
return toView(findByIdStmt.get(id));
|
return toView(findByIdStmt.get(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEffectiveVisibility(id) {
|
|
||||||
const row = effectiveVisibilityStmt.get(id);
|
|
||||||
return row ? row.effective_visibility : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function listVisible(loggedIn = false, isVip = false) {
|
function listVisible(loggedIn = false, isVip = false) {
|
||||||
return (isVip ? listVipStmt : loggedIn ? listUserStmt : listGuestStmt).all().map(toView);
|
return (isVip ? listVipStmt : loggedIn ? listUserStmt : listGuestStmt).all().map(toView);
|
||||||
}
|
}
|
||||||
|
|
@ -322,21 +261,11 @@ function setOrder(playlistId, songIds) {
|
||||||
const listPublicByUserGuestStmt = db.prepare(`
|
const listPublicByUserGuestStmt = db.prepare(`
|
||||||
SELECT p.* FROM playlists p
|
SELECT p.* FROM playlists p
|
||||||
WHERE p.created_by = ? AND p.visibility = 'public'
|
WHERE p.created_by = ? AND p.visibility = 'public'
|
||||||
AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM playlist_songs ps
|
|
||||||
JOIN songs s ON s.id = ps.song_id
|
|
||||||
WHERE ps.playlist_id = p.id AND s.visibility IN ('logged_in', 'vip', 'private')
|
|
||||||
)
|
|
||||||
ORDER BY p.created_at DESC
|
ORDER BY p.created_at DESC
|
||||||
`);
|
`);
|
||||||
const listPublicByUserLoggedStmt = db.prepare(`
|
const listPublicByUserLoggedStmt = db.prepare(`
|
||||||
SELECT p.* FROM playlists p
|
SELECT p.* FROM playlists p
|
||||||
WHERE p.created_by = ? AND p.visibility IN ('public', 'logged_in')
|
WHERE p.created_by = ? AND p.visibility IN ('public', 'logged_in')
|
||||||
AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM playlist_songs ps
|
|
||||||
JOIN songs s ON s.id = ps.song_id
|
|
||||||
WHERE ps.playlist_id = p.id AND s.visibility IN ('vip', 'private')
|
|
||||||
)
|
|
||||||
ORDER BY p.created_at DESC
|
ORDER BY p.created_at DESC
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
|
@ -385,7 +314,7 @@ function getSimilar(playlistId, loggedIn = false, isVip = false, limit = 5) {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
create, update, setCover, setVisibility, remove, findById, findBySlug, getEffectiveVisibility,
|
create, update, setCover, setVisibility, remove, findById, findBySlug,
|
||||||
listVisible, listAll, listByUser, listPublicByUser, featured,
|
listVisible, listAll, listByUser, listPublicByUser, featured,
|
||||||
getTracks, trackCount, addTrack, removeTrack, setOrder, getPlaylistsForSong,
|
getTracks, trackCount, addTrack, removeTrack, setOrder, getPlaylistsForSong,
|
||||||
updateTopGenres, reindexAllGenres, getSimilar,
|
updateTopGenres, reindexAllGenres, getSimilar,
|
||||||
|
|
|
||||||
|
|
@ -124,33 +124,18 @@ const likedPlaylistsGuestStmt = db.prepare(`
|
||||||
SELECT p.* FROM playlist_likes pl
|
SELECT p.* FROM playlist_likes pl
|
||||||
JOIN playlists p ON p.id = pl.playlist_id
|
JOIN playlists p ON p.id = pl.playlist_id
|
||||||
WHERE pl.user_id = ? AND p.visibility = 'public'
|
WHERE pl.user_id = ? AND p.visibility = 'public'
|
||||||
AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM playlist_songs ps
|
|
||||||
JOIN songs s ON s.id = ps.song_id
|
|
||||||
WHERE ps.playlist_id = p.id AND s.visibility IN ('logged_in', 'vip', 'private')
|
|
||||||
)
|
|
||||||
ORDER BY pl.created_at DESC
|
ORDER BY pl.created_at DESC
|
||||||
`);
|
`);
|
||||||
const likedPlaylistsUserStmt = db.prepare(`
|
const likedPlaylistsUserStmt = db.prepare(`
|
||||||
SELECT p.* FROM playlist_likes pl
|
SELECT p.* FROM playlist_likes pl
|
||||||
JOIN playlists p ON p.id = pl.playlist_id
|
JOIN playlists p ON p.id = pl.playlist_id
|
||||||
WHERE pl.user_id = ? AND p.visibility IN ('public', 'logged_in')
|
WHERE pl.user_id = ? AND p.visibility IN ('public', 'logged_in')
|
||||||
AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM playlist_songs ps
|
|
||||||
JOIN songs s ON s.id = ps.song_id
|
|
||||||
WHERE ps.playlist_id = p.id AND s.visibility IN ('vip', 'private')
|
|
||||||
)
|
|
||||||
ORDER BY pl.created_at DESC
|
ORDER BY pl.created_at DESC
|
||||||
`);
|
`);
|
||||||
const likedPlaylistsVipStmt = db.prepare(`
|
const likedPlaylistsVipStmt = db.prepare(`
|
||||||
SELECT p.* FROM playlist_likes pl
|
SELECT p.* FROM playlist_likes pl
|
||||||
JOIN playlists p ON p.id = pl.playlist_id
|
JOIN playlists p ON p.id = pl.playlist_id
|
||||||
WHERE pl.user_id = ? AND p.visibility IN ('public', 'logged_in', 'vip')
|
WHERE pl.user_id = ? AND p.visibility IN ('public', 'logged_in', 'vip')
|
||||||
AND NOT EXISTS (
|
|
||||||
SELECT 1 FROM playlist_songs ps
|
|
||||||
JOIN songs s ON s.id = ps.song_id
|
|
||||||
WHERE ps.playlist_id = p.id AND s.visibility = 'private'
|
|
||||||
)
|
|
||||||
ORDER BY pl.created_at DESC
|
ORDER BY pl.created_at DESC
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue