206 lines
6.5 KiB
JavaScript
206 lines
6.5 KiB
JavaScript
const { db } = require('../../db');
|
|
|
|
const insertStmt = db.prepare(`
|
|
INSERT INTO playlists (title, description, cover_path, visibility, created_by)
|
|
VALUES (@title, @description, @cover_path, @visibility, @created_by)
|
|
`);
|
|
const updateStmt = db.prepare(`
|
|
UPDATE playlists SET title = @title, description = @description, visibility = @visibility
|
|
WHERE id = @id
|
|
`);
|
|
const updateCoverStmt = db.prepare('UPDATE playlists SET cover_path = ? WHERE id = ?');
|
|
const setVisibilityStmt = db.prepare('UPDATE playlists SET visibility = ? WHERE id = ?');
|
|
const deleteStmt = db.prepare('DELETE 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 or private.
|
|
const listGuestStmt = db.prepare(`
|
|
SELECT p.* FROM playlists p
|
|
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', 'private')
|
|
)
|
|
ORDER BY p.created_at DESC
|
|
`);
|
|
// Logged-in users see public/logged_in playlists where nothing in them is private.
|
|
const listUserStmt = db.prepare(`
|
|
SELECT p.* FROM playlists p
|
|
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 = 'private'
|
|
)
|
|
ORDER BY p.created_at DESC
|
|
`);
|
|
const listAllStmt = db.prepare('SELECT * FROM playlists ORDER BY created_at DESC');
|
|
const listByUserStmt = db.prepare('SELECT * FROM playlists WHERE created_by = ? ORDER BY created_at DESC');
|
|
const featuredGuestStmt = db.prepare(`
|
|
SELECT p.* FROM playlists p
|
|
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', 'private')
|
|
)
|
|
ORDER BY RANDOM() LIMIT ?
|
|
`);
|
|
const featuredUserStmt = db.prepare(`
|
|
SELECT p.* FROM playlists p
|
|
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 = 'private'
|
|
)
|
|
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 = '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 tracksStmt = db.prepare(`
|
|
SELECT s.*, ps.position FROM playlist_songs ps
|
|
JOIN songs s ON s.id = ps.song_id
|
|
WHERE ps.playlist_id = ?
|
|
ORDER BY ps.position ASC, s.title ASC
|
|
`);
|
|
const maxPosStmt = db.prepare('SELECT COALESCE(MAX(position), 0) AS m FROM playlist_songs WHERE playlist_id = ?');
|
|
const insertTrackStmt = db.prepare('INSERT OR IGNORE INTO playlist_songs (playlist_id, song_id, position) VALUES (?, ?, ?)');
|
|
const removeTrackStmt = db.prepare('DELETE FROM playlist_songs WHERE playlist_id = ? AND song_id = ?');
|
|
const clearTracksStmt = db.prepare('DELETE FROM playlist_songs WHERE playlist_id = ?');
|
|
const countStmt = db.prepare('SELECT COUNT(*) AS c FROM playlist_songs WHERE playlist_id = ?');
|
|
const creatorNameStmt = db.prepare('SELECT display_name FROM users WHERE id = ?');
|
|
|
|
function toView(row) {
|
|
if (!row) return null;
|
|
return {
|
|
id: row.id,
|
|
title: row.title,
|
|
description: row.description,
|
|
coverPath: row.cover_path,
|
|
visibility: row.visibility || 'logged_in',
|
|
createdBy: row.created_by,
|
|
createdByName: row.created_by ? (creatorNameStmt.get(row.created_by)?.display_name || null) : null,
|
|
createdAt: row.created_at,
|
|
};
|
|
}
|
|
|
|
function trackView(row) {
|
|
return {
|
|
id: row.id,
|
|
title: row.title,
|
|
artist: row.artist,
|
|
album: row.album,
|
|
durationSeconds: row.duration_seconds,
|
|
coverPath: row.cover_path,
|
|
visibility: row.visibility || 'logged_in',
|
|
uploadedBy: row.uploaded_by,
|
|
position: row.position,
|
|
};
|
|
}
|
|
|
|
function create(data) {
|
|
const info = insertStmt.run({
|
|
title: data.title,
|
|
description: data.description || null,
|
|
cover_path: data.coverPath || null,
|
|
visibility: data.visibility || 'logged_in',
|
|
created_by: data.createdBy || null,
|
|
});
|
|
return toView(findByIdStmt.get(info.lastInsertRowid));
|
|
}
|
|
|
|
function update(id, data) {
|
|
updateStmt.run({
|
|
id,
|
|
title: data.title,
|
|
description: data.description || null,
|
|
visibility: data.visibility || 'logged_in',
|
|
});
|
|
return toView(findByIdStmt.get(id));
|
|
}
|
|
|
|
function setCover(id, coverPath) {
|
|
updateCoverStmt.run(coverPath, id);
|
|
}
|
|
|
|
function setVisibility(id, visibility) {
|
|
setVisibilityStmt.run(visibility, id);
|
|
}
|
|
|
|
function remove(id) {
|
|
deleteStmt.run(id);
|
|
}
|
|
|
|
function findById(id) {
|
|
return toView(findByIdStmt.get(id));
|
|
}
|
|
|
|
function getEffectiveVisibility(id) {
|
|
const row = effectiveVisibilityStmt.get(id);
|
|
return row ? row.effective_visibility : null;
|
|
}
|
|
|
|
function listVisible(loggedIn = false) {
|
|
return (loggedIn ? listUserStmt : listGuestStmt).all().map(toView);
|
|
}
|
|
|
|
function listAll() {
|
|
return listAllStmt.all().map(toView);
|
|
}
|
|
|
|
function listByUser(userId) {
|
|
return listByUserStmt.all(userId).map(toView);
|
|
}
|
|
|
|
function featured(n = 6, loggedIn = false) {
|
|
return (loggedIn ? featuredUserStmt : featuredGuestStmt).all(n).map(toView);
|
|
}
|
|
|
|
function getTracks(playlistId) {
|
|
return tracksStmt.all(playlistId).map(trackView);
|
|
}
|
|
|
|
function trackCount(playlistId) {
|
|
return countStmt.get(playlistId).c;
|
|
}
|
|
|
|
function addTrack(playlistId, songId) {
|
|
const nextPos = maxPosStmt.get(playlistId).m + 1;
|
|
insertTrackStmt.run(playlistId, songId, nextPos);
|
|
}
|
|
|
|
function removeTrack(playlistId, songId) {
|
|
removeTrackStmt.run(playlistId, songId);
|
|
}
|
|
|
|
function setOrder(playlistId, songIds) {
|
|
const tx = db.transaction(() => {
|
|
clearTracksStmt.run(playlistId);
|
|
songIds.forEach((songId, idx) => insertTrackStmt.run(playlistId, songId, idx + 1));
|
|
});
|
|
tx();
|
|
}
|
|
|
|
module.exports = {
|
|
create, update, setCover, setVisibility, remove, findById, getEffectiveVisibility,
|
|
listVisible, listAll, listByUser, featured,
|
|
getTracks, trackCount, addTrack, removeTrack, setOrder,
|
|
};
|