From 41fff1d406e3d9a67c2abab763ed6b8012b03685 Mon Sep 17 00:00:00 2001 From: Kaushik Narayan R Date: Fri, 9 Aug 2024 01:01:17 +0530 Subject: [PATCH] another day, another correction/improvement/refactor commit --- {utils => api}/axios.js | 3 +- api/spotify.js | 140 +++++++++++++++++++++++++++ controllers/apiCall.js | 69 -------------- controllers/auth.js | 14 +-- controllers/operations.js | 195 ++++++++++++-------------------------- controllers/playlists.js | 53 +++-------- routes/operations.js | 12 +-- routes/playlists.js | 6 +- utils/graph.js | 4 +- utils/logger.js | 2 +- validators/operations.js | 4 +- 11 files changed, 238 insertions(+), 264 deletions(-) rename {utils => api}/axios.js (93%) create mode 100644 api/spotify.js delete mode 100644 controllers/apiCall.js diff --git a/utils/axios.js b/api/axios.js similarity index 93% rename from utils/axios.js rename to api/axios.js index 294d428..d07585e 100644 --- a/utils/axios.js +++ b/api/axios.js @@ -1,7 +1,7 @@ const axios = require('axios'); const { baseAPIURL, accountsAPIURL } = require("../constants"); -const logger = require('./logger')(module); +const logger = require('../utils/logger')(module); const authInstance = axios.default.create({ baseURL: accountsAPIURL, @@ -38,7 +38,6 @@ axiosInstance.interceptors.response.use( name: error.name, code: error.code, message: error.message, - stack: error.stack, }, req: error.config, }); diff --git a/api/spotify.js b/api/spotify.js new file mode 100644 index 0000000..a93a297 --- /dev/null +++ b/api/spotify.js @@ -0,0 +1,140 @@ + +const logger = require("../utils/logger")(module); + +const typedefs = require("../typedefs"); + +const { axiosInstance } = require("./axios"); + +const logPrefix = "Spotify API: "; + +/** + * Spotify API - one-off request handler + * @param {typedefs.Req} req convenient auto-placing headers from middleware (not a good approach?) + * @param {typedefs.Res} res handle failure responses here itself (not a good approach?) + * @param {import('axios').Method} method HTTP method + * @param {string} path request path + * @param {import('axios').AxiosRequestConfig} config request params, headers, etc. + * @param {any} data request body + * @param {boolean} inlineData true if data is to be placed inside config + */ +const singleRequest = async (req, res, method, path, config = {}, data = null, inlineData = false) => { + let resp; + config.headers = { ...config.headers, ...req.sessHeaders }; + try { + if (!data || (data && inlineData)) { + if (data) + config.data = data ?? null; + resp = await axiosInstance[method.toLowerCase()](path, config); + } else + resp = await axiosInstance[method.toLowerCase()](path, data, config); + + logger.debug(logPrefix + "Successful response received."); + return resp; + } catch (error) { + if (error.response) { + // Non 2XX response received + let logMsg; + if (error.response.status >= 400 && error.response.status < 600) { + res.status(error.response.status).send(error.response.data); + logMsg = '' + error.response.status + } + else { + res.sendStatus(error.response.status); + logMsg = "???"; + } + logger.error(logPrefix + logMsg, { + response: { + data: error.response.data, + status: error.response.status, + } + }); + } else if (error.request) { + // No response received + res.sendStatus(504); + logger.warn(logPrefix + "No response", { error }); + } else { + // Something happened in setting up the request that triggered an Error + res.sendStatus(500); + logger.error(logPrefix + "Request failed?", { error }); + } + + return null; + }; +} + +const getUserProfile = async (req, res) => { + const response = await singleRequest(req, res, + "GET", "/me", + { headers: { Authorization: `Bearer ${req.session.accessToken}` } } + ); + return res.headersSent ? null : response.data; +} + +const getUserPlaylistsFirstPage = async (req, res) => { + const response = await singleRequest(req, res, + "GET", + `/users/${req.session.user.id}/playlists`, + { + params: { + offset: 0, + limit: 50, + }, + }); + return res.headersSent ? null : response.data; +} + +const getUserPlaylistsNextPage = async (req, res, nextURL) => { + const response = await singleRequest( + req, res, "GET", nextURL); + return res.headersSent ? null : response.data; +} + +const getPlaylistDetailsFirstPage = async (req, res, initialFields, playlistID) => { + const response = await singleRequest(req, res, + "GET", + `/playlists/${playlistID}/`, + { + params: { + fields: initialFields + }, + }); + return res.headersSent ? null : response.data; +} + +const getPlaylistDetailsNextPage = async (req, res, nextURL) => { + const response = await singleRequest( + req, res, "GET", nextURL); + return res.headersSent ? null : response.data; +} + +const addItemsToPlaylist = async (req, res, nextBatch, playlistID) => { + const response = await singleRequest(req, res, + "POST", + `/playlists/${playlistID}/tracks`, + {}, + { uris: nextBatch }, false + ) + return res.headersSent ? null : response.data; +} + +const removeItemsFromPlaylist = async (req, res, nextBatch, playlistID, snapshotID) => { + const response = await singleRequest(req, res, + "DELETE", + `/playlists/${playlistID}/tracks`, + {}, + // axios delete method doesn't have separate arg for body so hv to put it in config + { positions: nextBatch, snapshot_id: snapshotID }, true + ); + return res.headersSent ? null : response.data; +} + +module.exports = { + singleRequest, + getUserProfile, + getUserPlaylistsFirstPage, + getUserPlaylistsNextPage, + getPlaylistDetailsFirstPage, + getPlaylistDetailsNextPage, + addItemsToPlaylist, + removeItemsFromPlaylist, +} \ No newline at end of file diff --git a/controllers/apiCall.js b/controllers/apiCall.js deleted file mode 100644 index f45ad46..0000000 --- a/controllers/apiCall.js +++ /dev/null @@ -1,69 +0,0 @@ - -const logger = require("../utils/logger")(module); - -const typedefs = require("../typedefs"); - -const { axiosInstance } = require("../utils/axios"); - -const logPrefix = "Spotify API: "; - -/** - * Spotify API - one-off request handler - * @param {typedefs.Req} req convenient auto-placing headers from middleware (not a good approach?) - * @param {typedefs.Res} res handle failure responses here itself (not a good approach?) - * @param {import('axios').Method} method HTTP method - * @param {string} path request path - * @param {import('axios').AxiosRequestConfig} config request params, headers, etc. - * @param {any} data request body - * @param {boolean} inlineData true if data is to be placed inside config - * @returns {Promise<{ success: boolean, resp: any }>} - */ -const singleRequest = async (req, res, method, path, config = {}, data = null, inlineData = false) => { - let resp; - config.headers = { ...config.headers, ...req.sessHeaders }; - try { - if (!data || (data && inlineData)) { - if (data) - config.data = data ?? null; - resp = await axiosInstance[method.toLowerCase()](path, config); - } else - resp = await axiosInstance[method.toLowerCase()](path, data, config); - - logger.debug(logPrefix + "Successful response received."); - return { success: true, resp }; - } catch (error) { - if (error.response) { - // Non 2XX response received - let logMsg; - if (error.response.status >= 400 && error.response.status < 600) { - res.status(error.response.status).send(error.response.data); - logMsg = '' + error.response.status - } - else { - res.sendStatus(error.response.status); - logMsg = "???"; - } - logger.error(logPrefix + logMsg, { - response: { - data: error.response.data, - status: error.response.status, - } - }); - } else if (error.request) { - // No response received - res.sendStatus(504); - logger.warn(logPrefix + "No response"); - } else { - // Something happened in setting up the request that triggered an Error - res.sendStatus(500); - logger.error(logPrefix + "Request failed?"); - } - - return { success: false }; - }; -} - - -module.exports = { - singleRequest, -} \ No newline at end of file diff --git a/controllers/auth.js b/controllers/auth.js index 3d8e317..7277504 100644 --- a/controllers/auth.js +++ b/controllers/auth.js @@ -1,10 +1,10 @@ -const { authInstance, axiosInstance } = require("../utils/axios"); +const { authInstance } = require("../api/axios"); const typedefs = require("../typedefs"); -const { scopes, stateKey, accountsAPIURL, sessionAgeInSeconds, sessionName } = require('../constants'); +const { scopes, stateKey, accountsAPIURL, sessionName } = require('../constants'); const generateRandString = require('../utils/generateRandString'); -const { singleRequest } = require("./apiCall"); +const { getUserProfile } = require("../api/spotify"); const logger = require('../utils/logger')(module); /** @@ -79,12 +79,8 @@ const callback = async (req, res) => { res.status(tokenResponse.status).send('Error: Login failed'); } - const userResp = await singleRequest(req, res, - "GET", "/me", - { headers: { Authorization: `Bearer ${req.session.accessToken}` } } - ); - if (!userResp.success) return; - const userData = userResp.resp.data; + const userData = await getUserProfile(req, res); + if (res.headersSent) return; /** @type {typedefs.User} */ req.session.user = { diff --git a/controllers/operations.js b/controllers/operations.js index 4c2f070..efc5a3c 100644 --- a/controllers/operations.js +++ b/controllers/operations.js @@ -1,7 +1,7 @@ const typedefs = require("../typedefs"); const logger = require("../utils/logger")(module); -const { singleRequest } = require("./apiCall"); +const { getUserPlaylistsFirstPage, getUserPlaylistsNextPage, getPlaylistDetailsFirstPage, getPlaylistDetailsNextPage, removeItemsFromPlaylist } = require("../api/spotify"); const { parseSpotifyLink } = require("../utils/spotifyURITransformer"); const myGraph = require("../utils/graph"); @@ -22,17 +22,8 @@ const updateUser = async (req, res) => { const uID = req.session.user.id; // get first 50 - const response = await singleRequest(req, res, - "GET", - `/users/${uID}/playlists`, - { - params: { - offset: 0, - limit: 50, - }, - }); - if (!response.success) return; - const respData = response.resp.data; + const respData = await getUserPlaylistsFirstPage(req, res); + if (res.headersSent) return; currentPlaylists = respData.items.map(playlist => { return { @@ -44,9 +35,8 @@ const updateUser = async (req, res) => { // keep getting batches of 50 till exhausted while (nextURL) { - const nextResp = await singleRequest(req, res, "GET", nextURL); - if (!nextResp.success) return; - const nextData = nextResp.resp.data; + const nextData = await getUserPlaylistsNextPage(req, res, nextURL); + if (res.headersSent) return; currentPlaylists.push( ...nextData.items.map(playlist => { @@ -193,7 +183,7 @@ const createLink = async (req, res) => { return; } } catch (error) { - res.status(400).send({ message: "Invalid Spotify playlist link" }); + res.status(400).send({ message: error.message }); logger.error("parseSpotifyLink", { error }); return; } @@ -283,7 +273,7 @@ const removeLink = async (req, res) => { return; } } catch (error) { - res.status(400).send({ message: "Invalid Spotify playlist link" }); + res.status(400).send({ message: error.message }); logger.error("parseSpotifyLink", { error }); return; } @@ -329,6 +319,7 @@ const removeLink = async (req, res) => { } } + /** * Add tracks to the link-head playlist, * that are present in the link-tail playlist but not in the link-head playlist, @@ -349,21 +340,22 @@ const removeLink = async (req, res) => { * @param {typedefs.Req} req * @param {typedefs.Res} res */ -const populateMissingInLink = async (req, res) => { +const populateSingleLink = async (req, res) => { try { + let fromPl, toPl; + const link = { from: req.body.from, to: req.body.to }; const uID = req.session.user.id; - let fromPl, toPl; try { - fromPl = parseSpotifyLink(req.body["from"]); - toPl = parseSpotifyLink(req.body["to"]); + fromPl = parseSpotifyLink(req.body.from); + toPl = parseSpotifyLink(req.body.to); if (fromPl.type !== "playlist" || toPl.type !== "playlist") { res.status(400).send({ message: "Link is not a playlist" }); - logger.warn("non-playlist link provided", { from: fromPl, to: toPl }); + logger.warn("non-playlist link provided", link); return; } } catch (error) { - res.status(400).send({ message: "Invalid Spotify playlist link" }); + res.status(400).send({ message: error.message }); logger.error("parseSpotifyLink", { error }); return; } @@ -380,47 +372,30 @@ const populateMissingInLink = async (req, res) => { }); if (!existingLink) { res.sendStatus(409); - logger.error("link does not exist"); + logger.warn("link does not exist", { link }); return; } let checkFields = ["collaborative", "owner(id)"]; - - const checkResp = await singleRequest(req, res, - "GET", - `/playlists/${fromPl.id}/`, - { - params: { - fields: checkFields.join() - }, - }); - if (!checkResp.success) return; - - const checkFromData = checkResp.resp.data; + const checkFromData = await getPlaylistDetailsFirstPage(req, res, checkFields.join(), fromPl.id); + if (res.headersSent) return; // editable = collaborative || user is owner if (checkFromData.collaborative !== true && checkFromData.owner.id !== uID) { res.status(403).send({ - message: "You cannot edit this playlist, you must be owner/playlist must be collaborative" + message: "You cannot edit this playlist, you must be owner/playlist must be collaborative", + playlistID: fromPl.id }); - logger.error("user cannot edit target playlist"); + logger.warn("user cannot edit target playlist", { playlistID: fromPl.id }); return; } let initialFields = ["tracks(next,items(is_local,track(uri)))"]; let mainFields = ["next", "items(is_local,track(uri))"]; - const fromResp = await singleRequest(req, res, - "GET", - `/playlists/${fromPl.id}/`, - { - params: { - fields: initialFields.join() - }, - }); - if (!fromResp.success) return; - const fromData = fromResp.resp.data; + const fromData = await getPlaylistDetailsFirstPage(req, res, initialFields.join(), fromPl.id); + if (res.headersSent) return; let fromPlaylist = {}; // varying fields again smh @@ -439,10 +414,8 @@ const populateMissingInLink = async (req, res) => { // keep getting batches of 50 till exhausted while (fromPlaylist.next) { - const nextResp = await singleRequest(req, res, - "GET", fromPlaylist.next); - if (!nextResp.success) return; - const nextData = nextResp.resp.data; + const nextData = await getPlaylistDetailsNextPage(req, res, fromPlaylist.next); + if (res.headersSent) return; fromPlaylist.tracks.push( ...nextData.items.map((playlist_item) => { @@ -458,16 +431,8 @@ const populateMissingInLink = async (req, res) => { delete fromPlaylist.next; - const toResp = await singleRequest(req, res, - "GET", - `/playlists/${toPl.id}/`, - { - params: { - fields: initialFields.join() - }, - }); - if (!toResp.success) return; - const toData = toResp.resp.data; + const toData = await getPlaylistDetailsFirstPage(req, res, initialFields.join(), toPl.id); + if (res.headersSent) return; let toPlaylist = {}; // varying fields again smh @@ -485,10 +450,8 @@ const populateMissingInLink = async (req, res) => { // keep getting batches of 50 till exhausted while (toPlaylist.next) { - const nextResp = await singleRequest(req, res, - "GET", toPlaylist.next); - if (!nextResp.success) return; - const nextData = nextResp.resp.data; + const nextData = await getPlaylistDetailsNextPage(req, res, toPlaylist.next); + if (res.headersSent) return; toPlaylist.tracks.push( ...nextData.items.map((playlist_item) => { return { @@ -509,26 +472,26 @@ const populateMissingInLink = async (req, res) => { filter(track => !fromTrackURIs.includes(track.uri)). // only ones missing from the 'from' playlist map(track => track.uri); - const logNum = toTrackURIs.length; + const toAddNum = toTrackURIs.length; + const localNum = toPlaylist.tracks.filter(track => track.is_local).length; // append to end in batches of 100 while (toTrackURIs.length) { const nextBatch = toTrackURIs.splice(0, 100); - const addResponse = await singleRequest(req, res, - "POST", - `/playlists/${fromPl.id}/tracks`, - {}, - { uris: nextBatch }, false - ); - if (!addResponse.success) return; + const addData = await addItemsToPlaylist(req, res, nextBatch, fromPl.id); + if (res.headersSent) return; } - res.status(200).send({ message: `Added ${logNum} tracks.` }); - logger.info(`Backfilled ${logNum} tracks`); + res.status(201).send({ + message: 'Added tracks.', + added: toAddNum, + local: localNum, + }); + logger.info(`Backfilled ${result.added} tracks, could not add ${result.local} local files.`); return; } catch (error) { res.sendStatus(500); - logger.error('populateMissingInLink', { error }); + logger.error('populateSingleLink', { error }); return; } } @@ -545,12 +508,12 @@ const populateMissingInLink = async (req, res) => { * * link from pl_a to pl_b exists * - * after pruneExcessInLink, pl_b will have tracks: b, c + * after pruneSingleLink, pl_b will have tracks: b, c * * @param {typedefs.Req} req * @param {typedefs.Res} res */ -const pruneExcessInLink = async (req, res) => { +const pruneSingleLink = async (req, res) => { try { const uID = req.session.user.id; @@ -564,7 +527,7 @@ const pruneExcessInLink = async (req, res) => { return } } catch (error) { - res.status(400).send({ message: "Invalid Spotify playlist link" }); + res.status(400).send({ message: error.message }); logger.error("parseSpotifyLink", { error }); return; } @@ -587,23 +550,15 @@ const pruneExcessInLink = async (req, res) => { let checkFields = ["collaborative", "owner(id)"]; - const checkToResp = await singleRequest(req, res, - "GET", - `/playlists/${toPl.id}/`, - { - params: { - fields: checkFields.join() - }, - }); - - if (!checkToResp.success) return; - const checkToData = checkToResp.resp.data; + const checkToData = await getPlaylistDetailsFirstPage(req, res, checkFields.join(), toPl.id); + if (res.headersSent) return; // editable = collaborative || user is owner if (checkToData.collaborative !== true && checkToData.owner.id !== uID) { res.status(403).send({ - message: "You cannot edit this playlist, you must be owner/playlist must be collaborative" + message: "You cannot edit this playlist, you must be owner/playlist must be collaborative", + playlistID: toPl.id }); logger.error("user cannot edit target playlist"); return; @@ -612,16 +567,8 @@ const pruneExcessInLink = async (req, res) => { let initialFields = ["snapshot_id", "tracks(next,items(is_local,track(uri)))"]; let mainFields = ["next", "items(is_local,track(uri))"]; - const fromResp = await singleRequest(req, res, - "GET", - `/playlists/${fromPl.id}/`, - { - params: { - fields: initialFields.join() - }, - }); - if (!fromResp.success) return; - const fromData = fromResp.resp.data; + const fromData = await getPlaylistDetailsFirstPage(req, res, initialFields.join(), fromPl.id); + if (res.headersSent) return; let fromPlaylist = {}; // varying fields again smh @@ -640,10 +587,8 @@ const pruneExcessInLink = async (req, res) => { // keep getting batches of 50 till exhausted while (fromPlaylist.next) { - const nextResp = await singleRequest(req, res, - "GET", fromPlaylist.next); - if (!nextResp.success) return; - const nextData = nextResp.resp.data; + const nextData = await getPlaylistDetailsNextPage(req, res, fromPlaylist.next); + if (res.headersSent) return; fromPlaylist.tracks.push( ...nextData.items.map((playlist_item) => { @@ -658,16 +603,10 @@ const pruneExcessInLink = async (req, res) => { } delete fromPlaylist.next; - const toResp = await singleRequest(req, res, - "GET", - `/playlists/${toPl.id}/`, - { - params: { - fields: initialFields.join() - }, - }); - if (!toResp.success) return; - const toData = toResp.resp.data; + + const toData = await getPlaylistDetailsFirstPage(req, res, initialFields.join(), toPl.id); + if (res.headersSent) return; + let toPlaylist = {}; // varying fields again smh toPlaylist.snapshot_id = toData.snapshot_id; @@ -685,10 +624,8 @@ const pruneExcessInLink = async (req, res) => { // keep getting batches of 50 till exhausted while (toPlaylist.next) { - const nextResp = await singleRequest(req, res, - "GET", toPlaylist.next); - if (!nextResp.success) return; - const nextData = nextResp.resp.data; + const nextData = await getPlaylistDetailsNextPage(req, res, toPlaylist.next); + if (res.headersSent) return; toPlaylist.tracks.push( ...nextData.items.map((playlist_item) => { @@ -720,15 +657,9 @@ const pruneExcessInLink = async (req, res) => { let currentSnapshot = toPlaylist.snapshot_id; while (indexes.length) { const nextBatch = indexes.splice(Math.max(indexes.length - 100, 0), 100); - const delResponse = await singleRequest(req, res, - "DELETE", - `/playlists/${toPl.id}/tracks`, - {}, - // axios delete method doesn't have separate arg for body so hv to put it in config - { positions: nextBatch, snapshot_id: currentSnapshot }, true - ) - if (!delResponse.success) return; - currentSnapshot = delResponse.resp.data.snapshot_id; + const delResponse = await removeItemsFromPlaylist(req, res, nextBatch, toPl.id, currentSnapshot); + if (res.headersSent) return; + currentSnapshot = delResponse.snapshot_id; } res.status(200).send({ message: `Removed ${logNum} tracks.` }); @@ -736,7 +667,7 @@ const pruneExcessInLink = async (req, res) => { return; } catch (error) { res.sendStatus(500); - logger.error('pruneExcessInLink', { error }); + logger.error('pruneSingleLink', { error }); return; } } @@ -746,6 +677,6 @@ module.exports = { fetchUser, createLink, removeLink, - populateMissingInLink, - pruneExcessInLink, + populateSingleLink, + pruneSingleLink, }; diff --git a/controllers/playlists.js b/controllers/playlists.js index 40361c5..72ab99d 100644 --- a/controllers/playlists.js +++ b/controllers/playlists.js @@ -1,7 +1,7 @@ const logger = require("../utils/logger")(module); const typedefs = require("../typedefs"); -const { singleRequest } = require("./apiCall"); +const { getUserPlaylistsFirstPage, getUserPlaylistsNextPage, getPlaylistDetailsFirstPage, getPlaylistDetailsNextPage } = require("../api/spotify"); const { parseSpotifyLink } = require("../utils/spotifyURITransformer"); /** @@ -9,22 +9,13 @@ const { parseSpotifyLink } = require("../utils/spotifyURITransformer"); * @param {typedefs.Req} req * @param {typedefs.Res} res */ -const getUserPlaylists = async (req, res) => { +const fetchUserPlaylists = async (req, res) => { try { let userPlaylists = {}; // get first 50 - const response = await singleRequest(req, res, - "GET", - `/users/${req.session.user.id}/playlists`, - { - params: { - offset: 0, - limit: 100, - }, - }); - if (!response.success) return; - const respData = response.resp.data; + const respData = await getUserPlaylistsFirstPage(req, res); + if (res.headersSent) return; userPlaylists.total = respData.total; @@ -40,10 +31,8 @@ const getUserPlaylists = async (req, res) => { userPlaylists.next = respData.next; // keep getting batches of 50 till exhausted while (userPlaylists.next) { - const nextResp = await singleRequest(req, res, - "GET", userPlaylists.next); - if (!nextResp.success) return; - const nextData = nextResp.resp.data; + const nextData = await getUserPlaylistsNextPage(req, res, userPlaylists.next); + if (res.headersSent) return; userPlaylists.items.push( ...nextData.items.map((playlist) => { @@ -66,7 +55,7 @@ const getUserPlaylists = async (req, res) => { return; } catch (error) { res.sendStatus(500); - logger.error('getUserPlaylists', { error }); + logger.error('fetchUserPlaylists', { error }); return; } } @@ -76,9 +65,8 @@ const getUserPlaylists = async (req, res) => { * @param {typedefs.Req} req * @param {typedefs.Res} res */ -const getPlaylistDetails = async (req, res) => { +const fetchPlaylistDetails = async (req, res) => { try { - /** @type {typedefs.PlaylistDetails} */ let playlist = {}; /** @type {typedefs.URIObject} */ let uri; @@ -94,21 +82,13 @@ const getPlaylistDetails = async (req, res) => { return; } } catch (error) { - res.status(400).send({ message: "Invalid Spotify playlist link" }); + res.status(400).send({ message: error.message }); logger.error("parseSpotifyLink", { error }); return; } - const response = await singleRequest(req, res, - "GET", - `/playlists/${uri.id}/`, - { - params: { - fields: initialFields.join() - }, - }); - if (!response.success) return; - const respData = response.resp.data; + const respData = await getPlaylistDetailsFirstPage(req, res, initialFields.join(), uri.id); + if (res.headersSent) return; // TODO: this whole section needs to be DRYer // look into serializr @@ -142,11 +122,8 @@ const getPlaylistDetails = async (req, res) => { // keep getting batches of 50 till exhausted while (playlist.next) { - const nextResp = await singleRequest(req, res, - "GET", playlist.next - ) - if (!nextResp.success) return; - const nextData = nextResp.resp.data; + const nextData = await getPlaylistDetailsNextPage(req, res, playlist.next); + if (res.headersSent) return; playlist.tracks.push( ...nextData.items.map((playlist_item) => { @@ -177,6 +154,6 @@ const getPlaylistDetails = async (req, res) => { } module.exports = { - getUserPlaylists, - getPlaylistDetails + fetchUserPlaylists, + fetchPlaylistDetails }; diff --git a/routes/operations.js b/routes/operations.js index 48bbbed..418bafc 100644 --- a/routes/operations.js +++ b/routes/operations.js @@ -1,8 +1,8 @@ const router = require('express').Router(); -const { updateUser, fetchUser, createLink, removeLink, populateMissingInLink, pruneExcessInLink } = require('../controllers/operations'); +const { updateUser, fetchUser, createLink, removeLink, populateSingleLink, pruneSingleLink } = require('../controllers/operations'); const { validate } = require('../validators'); -const { createLinkValidator, removeLinkValidator, populateMissingInLinkValidator, pruneExcessInLinkValidator } = require('../validators/operations'); +const { createLinkValidator, removeLinkValidator, populateSingleLinkValidator, pruneSingleLinkValidator } = require('../validators/operations'); router.put( "/update", @@ -30,16 +30,16 @@ router.delete( router.put( "/populate/link", - populateMissingInLinkValidator, + populateSingleLinkValidator, validate, - populateMissingInLink + populateSingleLink ); router.put( "/prune/link", - pruneExcessInLinkValidator, + pruneSingleLinkValidator, validate, - pruneExcessInLink + pruneSingleLink ); module.exports = router; diff --git a/routes/playlists.js b/routes/playlists.js index 13fa39c..d6e9190 100644 --- a/routes/playlists.js +++ b/routes/playlists.js @@ -1,19 +1,19 @@ const router = require('express').Router(); -const { getUserPlaylists, getPlaylistDetails } = require('../controllers/playlists'); +const { fetchUserPlaylists, fetchPlaylistDetails } = require('../controllers/playlists'); const { getPlaylistDetailsValidator } = require('../validators/playlists'); const { validate } = require("../validators"); router.get( "/me", - getUserPlaylists + fetchUserPlaylists ); router.get( "/details", getPlaylistDetailsValidator, validate, - getPlaylistDetails + fetchPlaylistDetails ); module.exports = router; diff --git a/utils/graph.js b/utils/graph.js index 8dae84e..02433ca 100644 --- a/utils/graph.js +++ b/utils/graph.js @@ -32,7 +32,7 @@ class myGraph { } /** - * @param {type} node + * @param {string} node * @returns {string[]} */ getDirectHeads(node) { @@ -40,7 +40,7 @@ class myGraph { } /** - * @param {type} node + * @param {string} node * @returns {string[]} */ getDirectTails(node) { diff --git a/utils/logger.js b/utils/logger.js index 6b8daba..3b46dce 100644 --- a/utils/logger.js +++ b/utils/logger.js @@ -28,7 +28,7 @@ const logFormat = printf(({ level, message, label, timestamp, ...meta }) => { } const { stack, ...rest } = meta.error; return `${timestamp} [${label}] ${level}: ${message}${metaFormat(rest)}\n` + - `${stack}`; + `${stack ?? ''}`; } return `${timestamp} [${label}] ${level}: ${message}${metaFormat(meta)}`; }); diff --git a/validators/operations.js b/validators/operations.js index dd71c4f..ed503de 100644 --- a/validators/operations.js +++ b/validators/operations.js @@ -26,6 +26,6 @@ const createLinkValidator = async (req, res, next) => { module.exports = { createLinkValidator, removeLinkValidator: createLinkValidator, - populateMissingInLinkValidator: createLinkValidator, - pruneExcessInLinkValidator: createLinkValidator, + populateSingleLinkValidator: createLinkValidator, + pruneSingleLinkValidator: createLinkValidator, }