From b2eb2a94fbbf43a580d60b92db9174382c72f2ad Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Wed, 5 Jul 2023 19:05:52 +0000 Subject: [PATCH] added `likes` logic --- .../playlists/routes/delete/:playlist_id.js | 8 +-- .../playlists/routes/get/:playlist_id/data.js | 17 ++++++- .../playlists/routes/put/playlist.js | 30 ++++++++++++ .../routes/post/:track_id/toggle-like.js | 46 +++++++++++++++++ .../src/models/track_like/index.js | 14 ++++++ .../music_server/src/services/removeTracks.js | 49 +++++++++++++++++++ 6 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 packages/music_server/src/controllers/tracks/routes/post/:track_id/toggle-like.js create mode 100644 packages/music_server/src/models/track_like/index.js create mode 100644 packages/music_server/src/services/removeTracks.js diff --git a/packages/music_server/src/controllers/playlists/routes/delete/:playlist_id.js b/packages/music_server/src/controllers/playlists/routes/delete/:playlist_id.js index 0816c03c..41a5e89f 100644 --- a/packages/music_server/src/controllers/playlists/routes/delete/:playlist_id.js +++ b/packages/music_server/src/controllers/playlists/routes/delete/:playlist_id.js @@ -1,11 +1,14 @@ import { Playlist, Track } from "@models" import { AuthorizationError, PermissionError, NotFoundError } from "@shared-classes/Errors" +import RemoveTracks from "@services/removeTracks" export default async (req, res) => { if (!req.session) { return new AuthorizationError(req, res) } + let removedTracksIds = [] + const removeWithTracks = req.query.remove_with_tracks === "true" let playlist = await Playlist.findOne({ @@ -27,12 +30,11 @@ export default async (req, res) => { }) if (removeWithTracks) { - await Track.deleteMany({ - _id: playlist.tracks, - }) + removedTracksIds = await RemoveTracks(playlist.list) } return res.json({ success: true, + removedTracksIds, }) } \ No newline at end of file diff --git a/packages/music_server/src/controllers/playlists/routes/get/:playlist_id/data.js b/packages/music_server/src/controllers/playlists/routes/get/:playlist_id/data.js index ed7bd6e2..5ab264f6 100644 --- a/packages/music_server/src/controllers/playlists/routes/get/:playlist_id/data.js +++ b/packages/music_server/src/controllers/playlists/routes/get/:playlist_id/data.js @@ -1,4 +1,4 @@ -import { Playlist, Track } from "@models" +import { Playlist, TrackLike, Track } from "@models" import { NotFoundError } from "@shared-classes/Errors" export default async (req, res) => { @@ -37,5 +37,20 @@ export default async (req, res) => { return orderedIds.findIndex((id) => id === a._id.toString()) - orderedIds.findIndex((id) => id === b._id.toString()) }) + if (req.session) { + const likes = await TrackLike.find({ + user_id: req.session.user_id, + track_id: [...playlist.list.map((track) => track._id.toString())], + }) + + playlist.list = playlist.list.map((track) => { + track = track.toObject() + + track.liked = likes.findIndex((like) => like.track_id === track._id.toString()) !== -1 + + return track + }) + } + return res.json(playlist) } \ No newline at end of file diff --git a/packages/music_server/src/controllers/playlists/routes/put/playlist.js b/packages/music_server/src/controllers/playlists/routes/put/playlist.js index 0393be11..c3ebe010 100644 --- a/packages/music_server/src/controllers/playlists/routes/put/playlist.js +++ b/packages/music_server/src/controllers/playlists/routes/put/playlist.js @@ -1,5 +1,6 @@ import { Playlist, Track } from "@models" import { AuthorizationError, NotFoundError, PermissionError, BadRequestError } from "@shared-classes/Errors" +import axios from "axios" const PlaylistAllowedUpdateFields = [ "title", @@ -23,6 +24,29 @@ const TrackAllowedUpdateFields = [ "public", ] +async function fetchTrackSourceMetadata(track) { + // get headers of source url (X-Amz-Meta-Duration) + const response = await axios({ + method: "HEAD", + url: track.source, + }).catch((err) => { + return { + data: null, + headers: null, + } + }) + + if (response.headers) { + return { + duration: response.headers["x-amz-meta-duration"], + size: response.headers["content-length"], + bitrate: response.headers["x-amz-meta-bitrate"], + } + } + + return null +} + async function createOrUpdateTrack(payload) { if (!payload.title || !payload.source || !payload.publisher) { throw new Error("title and source and publisher are required") @@ -54,6 +78,12 @@ async function createOrUpdateTrack(payload) { await track.save() } + if (!track.metadata) { + track.metadata = await fetchTrackSourceMetadata(track) + + await track.save() + } + return track } diff --git a/packages/music_server/src/controllers/tracks/routes/post/:track_id/toggle-like.js b/packages/music_server/src/controllers/tracks/routes/post/:track_id/toggle-like.js new file mode 100644 index 00000000..81c7dd52 --- /dev/null +++ b/packages/music_server/src/controllers/tracks/routes/post/:track_id/toggle-like.js @@ -0,0 +1,46 @@ +import { TrackLike, Track } from "@models" +import { AuthorizationError, NotFoundError } from "@shared-classes/Errors" + +export default async (req, res) => { + if (!req.session) { + return new AuthorizationError(req, res) + } + + const { track_id } = req.params + + const track = await Track.findById(track_id).catch((err) => { + return null + }) + + if (!track) { + return new NotFoundError(req, res, "Track not found") + } + + let like = await TrackLike.findOne({ + track_id: track_id, + user_id: req.session.user_id, + }) + + if (like) { + await like.delete() + like = null + } else { + like = new TrackLike({ + track_id: track_id, + user_id: req.session.user_id, + }) + + await like.save() + } + + global.ws.io.emit("music:self:track:toggle:like", { + track_id: track_id, + user_id: req.session.user_id, + action: like ? "liked" : "unliked", + }) + + return res.status(200).json({ + message: "ok", + action: like ? "liked" : "unliked", + }) +} \ No newline at end of file diff --git a/packages/music_server/src/models/track_like/index.js b/packages/music_server/src/models/track_like/index.js new file mode 100644 index 00000000..f9e91286 --- /dev/null +++ b/packages/music_server/src/models/track_like/index.js @@ -0,0 +1,14 @@ +export default { + name: "TrackLike", + collection: "tracks_likes", + schema: { + user_id: { + type: String, + required: true, + }, + track_id: { + type: String, + required: true, + } + } +} \ No newline at end of file diff --git a/packages/music_server/src/services/removeTracks.js b/packages/music_server/src/services/removeTracks.js new file mode 100644 index 00000000..42e57d1d --- /dev/null +++ b/packages/music_server/src/services/removeTracks.js @@ -0,0 +1,49 @@ +import { Track } from "@models" + +const urlRegex = new RegExp(`^https://(.*?)/(.*)$`) + +export default async (tracksIds) => { + if (typeof tracksIds === "string") { + tracksIds = [tracksIds] + } + + const removedIds = [] + + // find Tracks + const tracks = await Track.find({ + _id: tracksIds, + }) + + for (const track of tracks) { + const match = urlRegex.exec(track.source) + + const bucket = match[2].split("/")[0] + const objectName = match[2].split("/").slice(1).join("/") + + try { + // find on storage and remove + await new Promise((resolve, reject) => { + global.storage.removeObject(bucket, objectName, (err) => { + if (err) { + return reject(err) + } + + return resolve() + }) + }).catch((err) => { + console.error(err) + return false + }) + + // remove from db + await track.remove() + } catch (error) { + console.error(error) + continue + } + + removedIds.push(track._id) + } + + return removedIds +} \ No newline at end of file