Bri-Tunes/src/services/social.js

241 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
};