const { db } = require('../../db'); const notifications = require('./notifications'); // ── Lookup statements for notification context ─────────────────────────────── const songOwnerStmt = db.prepare('SELECT uploaded_by, title, slug FROM songs WHERE id = ?'); const playlistOwnerStmt = db.prepare('SELECT created_by, title, slug FROM playlists WHERE id = ?'); const userNameStmt = db.prepare('SELECT display_name FROM users WHERE id = ?'); function getActorName(actorId) { return actorId ? (userNameStmt.get(actorId)?.display_name || null) : null; } // ── Song statements ────────────────────────────────────────────────────────── const songLikeCountStmt = db.prepare('SELECT COUNT(*) AS c FROM song_likes WHERE song_id = ?'); const songFavCountStmt = db.prepare('SELECT COUNT(*) AS c FROM song_favorites WHERE song_id = ?'); const songUserLikedStmt = db.prepare('SELECT 1 FROM song_likes WHERE user_id = ? AND song_id = ?'); const songUserFavoritedStmt = db.prepare('SELECT 1 FROM song_favorites WHERE user_id = ? AND song_id = ?'); const insertSongLike = db.prepare('INSERT OR IGNORE INTO song_likes (user_id, song_id) VALUES (?, ?)'); const deleteSongLike = db.prepare('DELETE FROM song_likes WHERE user_id = ? AND song_id = ?'); const insertSongFav = db.prepare('INSERT OR IGNORE INTO song_favorites (user_id, song_id) VALUES (?, ?)'); const deleteSongFav = db.prepare('DELETE FROM song_favorites WHERE user_id = ? AND song_id = ?'); // ── Playlist statements ────────────────────────────────────────────────────── const plLikeCountStmt = db.prepare('SELECT COUNT(*) AS c FROM playlist_likes WHERE playlist_id = ?'); const plFavCountStmt = db.prepare('SELECT COUNT(*) AS c FROM playlist_favorites WHERE playlist_id = ?'); const plUserLikedStmt = db.prepare('SELECT 1 FROM playlist_likes WHERE user_id = ? AND playlist_id = ?'); const plUserFavoritedStmt = db.prepare('SELECT 1 FROM playlist_favorites WHERE user_id = ? AND playlist_id = ?'); const insertPlLike = db.prepare('INSERT OR IGNORE INTO playlist_likes (user_id, playlist_id) VALUES (?, ?)'); const deletePlLike = db.prepare('DELETE FROM playlist_likes WHERE user_id = ? AND playlist_id = ?'); const insertPlFav = db.prepare('INSERT OR IGNORE INTO playlist_favorites (user_id, playlist_id) VALUES (?, ?)'); const deletePlFav = db.prepare('DELETE FROM playlist_favorites WHERE user_id = ? AND playlist_id = ?'); // ── Toggle functions ───────────────────────────────────────────────────────── function toggleSongLike(userId, songId) { const exists = songUserLikedStmt.get(userId, songId); if (exists) { deleteSongLike.run(userId, songId); } else { insertSongLike.run(userId, songId); const song = songOwnerStmt.get(songId); if (song) notifications.create({ userId: song.uploaded_by, actorId: userId, actorName: getActorName(userId), action: 'like', entityType: 'song', entityId: songId, entityTitle: song.title, entitySlug: song.slug }); } return { liked: !exists, count: songLikeCountStmt.get(songId).c }; } function toggleSongFavorite(userId, songId) { const exists = songUserFavoritedStmt.get(userId, songId); if (exists) { deleteSongFav.run(userId, songId); } else { insertSongFav.run(userId, songId); const song = songOwnerStmt.get(songId); if (song) notifications.create({ userId: song.uploaded_by, actorId: userId, actorName: getActorName(userId), action: 'favorite', entityType: 'song', entityId: songId, entityTitle: song.title, entitySlug: song.slug }); } return { favorited: !exists, count: songFavCountStmt.get(songId).c }; } function togglePlaylistLike(userId, playlistId) { const exists = plUserLikedStmt.get(userId, playlistId); if (exists) { deletePlLike.run(userId, playlistId); } else { insertPlLike.run(userId, playlistId); const pl = playlistOwnerStmt.get(playlistId); if (pl) notifications.create({ userId: pl.created_by, actorId: userId, actorName: getActorName(userId), action: 'like', entityType: 'playlist', entityId: playlistId, entityTitle: pl.title, entitySlug: pl.slug }); } return { liked: !exists, count: plLikeCountStmt.get(playlistId).c }; } function togglePlaylistFavorite(userId, playlistId) { const exists = plUserFavoritedStmt.get(userId, playlistId); if (exists) { deletePlFav.run(userId, playlistId); } else { insertPlFav.run(userId, playlistId); const pl = playlistOwnerStmt.get(playlistId); if (pl) notifications.create({ userId: pl.created_by, actorId: userId, actorName: getActorName(userId), action: 'favorite', entityType: 'playlist', entityId: playlistId, entityTitle: pl.title, entitySlug: pl.slug }); } return { favorited: !exists, count: plFavCountStmt.get(playlistId).c }; } // ── Enrichment helpers ─────────────────────────────────────────────────────── // Mutates each item in-place, adding likeCount/favoriteCount/userLiked/userFavorited. function enrichSongs(songs, userId = null) { for (const s of songs) { s.likeCount = songLikeCountStmt.get(s.id).c; s.favoriteCount = songFavCountStmt.get(s.id).c; s.userLiked = userId ? !!songUserLikedStmt.get(userId, s.id) : false; s.userFavorited = userId ? !!songUserFavoritedStmt.get(userId, s.id) : false; } return songs; } function enrichPlaylists(playlists, userId = null) { for (const p of playlists) { p.likeCount = plLikeCountStmt.get(p.id).c; p.favoriteCount = plFavCountStmt.get(p.id).c; p.userLiked = userId ? !!plUserLikedStmt.get(userId, p.id) : false; p.userFavorited = userId ? !!plUserFavoritedStmt.get(userId, p.id) : false; } return playlists; } // ── Profile liked/favorited queries ───────────────────────────────────────── const likedSongsGuestStmt = db.prepare(` SELECT s.* FROM song_likes sl JOIN songs s ON s.id = sl.song_id WHERE sl.user_id = ? AND s.visibility = 'public' ORDER BY sl.created_at DESC `); const likedSongsUserStmt = db.prepare(` SELECT s.* FROM song_likes sl JOIN songs s ON s.id = sl.song_id WHERE sl.user_id = ? AND s.visibility IN ('public', 'logged_in') ORDER BY sl.created_at DESC `); const likedSongsVipStmt = db.prepare(` SELECT s.* FROM song_likes sl JOIN songs s ON s.id = sl.song_id WHERE sl.user_id = ? AND s.visibility IN ('public', 'logged_in', 'vip') ORDER BY sl.created_at DESC `); const likedPlaylistsGuestStmt = db.prepare(` SELECT p.* FROM playlist_likes pl JOIN playlists p ON p.id = pl.playlist_id 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 `); const likedPlaylistsUserStmt = db.prepare(` SELECT p.* FROM playlist_likes pl JOIN playlists p ON p.id = pl.playlist_id 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 `); const likedPlaylistsVipStmt = db.prepare(` SELECT p.* FROM playlist_likes pl JOIN playlists p ON p.id = pl.playlist_id 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 `); function getLikedSongs(userId, loggedIn = false, isVip = false) { return (isVip ? likedSongsVipStmt : loggedIn ? likedSongsUserStmt : likedSongsGuestStmt).all(userId); } function getLikedPlaylists(userId, loggedIn = false, isVip = false) { return (isVip ? likedPlaylistsVipStmt : loggedIn ? likedPlaylistsUserStmt : likedPlaylistsGuestStmt).all(userId); } // Site-wide recently liked songs (distinct songs, ordered by most recent like from any user). const recentlyLikedGuestStmt = db.prepare(` SELECT s.* FROM songs s WHERE s.visibility = 'public' AND EXISTS (SELECT 1 FROM song_likes sl WHERE sl.song_id = s.id) ORDER BY (SELECT MAX(sl2.created_at) FROM song_likes sl2 WHERE sl2.song_id = s.id) DESC LIMIT ? `); const recentlyLikedUserStmt = db.prepare(` SELECT s.* FROM songs s WHERE s.visibility IN ('public', 'logged_in') AND EXISTS (SELECT 1 FROM song_likes sl WHERE sl.song_id = s.id) ORDER BY (SELECT MAX(sl2.created_at) FROM song_likes sl2 WHERE sl2.song_id = s.id) DESC LIMIT ? `); const recentlyLikedVipStmt = db.prepare(` SELECT s.* FROM songs s WHERE s.visibility IN ('public', 'logged_in', 'vip') AND EXISTS (SELECT 1 FROM song_likes sl WHERE sl.song_id = s.id) ORDER BY (SELECT MAX(sl2.created_at) FROM song_likes sl2 WHERE sl2.song_id = s.id) DESC LIMIT ? `); function getRecentlyLikedSongs(n = 5, loggedIn = false, isVip = false) { return (isVip ? recentlyLikedVipStmt : loggedIn ? recentlyLikedUserStmt : recentlyLikedGuestStmt).all(n); } // ── Most popular songs (scored by likes + 2×favorites) ─────────────────────── const mostPopularGuestStmt = db.prepare(` SELECT s.*, (COUNT(DISTINCT sl.user_id) + 2 * COUNT(DISTINCT sf.user_id)) AS score FROM songs s LEFT JOIN song_likes sl ON sl.song_id = s.id LEFT JOIN song_favorites sf ON sf.song_id = s.id WHERE s.visibility = 'public' GROUP BY s.id HAVING score > 0 ORDER BY score DESC, s.created_at DESC LIMIT ? `); const mostPopularUserStmt = db.prepare(` SELECT s.*, (COUNT(DISTINCT sl.user_id) + 2 * COUNT(DISTINCT sf.user_id)) AS score FROM songs s LEFT JOIN song_likes sl ON sl.song_id = s.id LEFT JOIN song_favorites sf ON sf.song_id = s.id WHERE s.visibility IN ('public', 'logged_in') GROUP BY s.id HAVING score > 0 ORDER BY score DESC, s.created_at DESC LIMIT ? `); const mostPopularVipStmt = db.prepare(` SELECT s.*, (COUNT(DISTINCT sl.user_id) + 2 * COUNT(DISTINCT sf.user_id)) AS score FROM songs s LEFT JOIN song_likes sl ON sl.song_id = s.id LEFT JOIN song_favorites sf ON sf.song_id = s.id WHERE s.visibility IN ('public', 'logged_in', 'vip') GROUP BY s.id HAVING score > 0 ORDER BY score DESC, s.created_at DESC LIMIT ? `); function getMostPopular(n = 20, loggedIn = false, isVip = false) { return (isVip ? mostPopularVipStmt : loggedIn ? mostPopularUserStmt : mostPopularGuestStmt).all(n); } module.exports = { toggleSongLike, toggleSongFavorite, togglePlaylistLike, togglePlaylistFavorite, enrichSongs, enrichPlaylists, getLikedSongs, getLikedPlaylists, getRecentlyLikedSongs, getMostPopular, };