package (and their typing) updates

This commit is contained in:
Kaushik Narayan R 2025-05-31 22:02:29 -07:00
parent 5e4396031e
commit 935a18f2cc
11 changed files with 485 additions and 653 deletions

View File

@ -4,11 +4,11 @@ import logger from "../utils/logger.ts";
const __controller_func: RequestHandler = async (req, res) => {
try {
return null;
return;
} catch (error) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("__controller_func", { error });
return null;
return;
}
};

View File

@ -9,7 +9,8 @@ if (!process.env["SPOTMGR_REDIS_URI"])
const redisClient: ReturnType<typeof createClient> = createClient({
url: process.env["SPOTMGR_REDIS_URI"],
socket: {
keepAlive: 25 * 1000, // 25s
keepAlive: true,
keepAliveInitialDelay: 25 * 1000, // 25s
connectTimeout: 15 * 1000,
},
});

View File

@ -31,11 +31,11 @@ const login: RequestHandler = async (_req, res) => {
state: state,
} as Record<string, string>).toString()
);
return null;
return;
} catch (error) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("login", { error });
return null;
return;
}
};
@ -52,11 +52,11 @@ const callback: RequestHandler = async (req, res) => {
if (state === null || state !== storedState) {
res.status(409).send({ message: "Invalid state" });
logger.warn("state mismatch");
return null;
return;
} else if (error) {
res.status(401).send({ message: "Auth callback error" });
logger.error("callback error", { error });
return null;
return;
} else {
// get auth tokens
res.clearCookie(stateKey);
@ -83,14 +83,14 @@ const callback: RequestHandler = async (req, res) => {
.status(tokenResponse.status)
.send({ message: "Error: Login failed" });
logger.error("login failed", { statusCode: tokenResponse.status });
return null;
return;
}
const { resp } = await getCurrentUsersProfile({
res,
authHeaders,
});
if (!resp) return null;
if (!resp) return;
req.session.user = {
username: resp.data.display_name ?? "",
@ -105,14 +105,14 @@ const callback: RequestHandler = async (req, res) => {
res.redirect(process.env["SPOTMGR_APP_URI"] + "?login=success");
logger.debug("New login.", { username: resp.data.display_name });
}
return null;
return;
});
return null;
return;
}
} catch (error) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("callback", { error });
return null;
return;
}
};
@ -144,7 +144,7 @@ const refresh: RequestHandler = async (req, res) => {
: ""
}.`
);
return null;
return;
} else {
res
.status(response.status)
@ -153,12 +153,12 @@ const refresh: RequestHandler = async (req, res) => {
statusCode: response.status,
data: response.data,
});
return null;
return;
}
} catch (error) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("refresh", { error });
return null;
return;
}
};
@ -178,11 +178,11 @@ const logout: RequestHandler = async (req, res) => {
logger.debug("Logged out.", { sessionID: delSession.id });
}
});
return null;
return;
} catch (error) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("logout", { error });
return null;
return;
}
};

View File

@ -33,11 +33,11 @@ const exportData: RequestHandler = async (req, res) => {
);
res.send(JSON.stringify({ currentPlaylists, currentLinks }));
logger.debug("exported data");
return null;
return;
} catch (error) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("exportData", { error });
return null;
return;
}
};
@ -50,7 +50,7 @@ const importData: RequestHandler = async (req, res) => {
if (!req.file) {
res.status(400).send({ message: "No file provided!" });
logger.debug("no file provided");
return null;
return;
}
let exportedLinks: Pick<Links, "from" | "to">[];
let exportedPls: Pick<Playlists, "playlistID" | "playlistName">[];
@ -62,7 +62,7 @@ const importData: RequestHandler = async (req, res) => {
const message = "Could not parse data file";
res.status(400).send({ message });
logger.info(message, { error });
return null;
return;
}
const delPlNum = await Playlists.destroy({ where: { userID: uID } });
@ -100,11 +100,11 @@ const importData: RequestHandler = async (req, res) => {
links: addedLinks.length,
playlists: addedPls.length,
});
return null;
return;
} catch (error) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("exportData", { error });
return null;
return;
}
};

View File

@ -47,7 +47,7 @@ const updateUser: RequestHandler = async (req, res) => {
authHeaders,
res,
});
if (!resp) return null;
if (!resp) return;
const respData = resp.data;
currentPlaylists = respData.items.map((playlist) => {
@ -65,7 +65,7 @@ const updateUser: RequestHandler = async (req, res) => {
res,
nextURL,
});
if (!resp) return null;
if (!resp) return;
const nextData = resp.data;
currentPlaylists.push(
@ -155,7 +155,7 @@ const updateUser: RequestHandler = async (req, res) => {
if (delNum !== deleted.length) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("Could not remove all old playlists");
return null;
return;
}
}
@ -169,7 +169,7 @@ const updateUser: RequestHandler = async (req, res) => {
if (addPls.length !== added.length) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("Could not add all new playlists");
return null;
return;
}
}
@ -186,7 +186,7 @@ const updateUser: RequestHandler = async (req, res) => {
} catch (error) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("Could not update playlist names");
return null;
return;
}
res
@ -198,11 +198,11 @@ const updateUser: RequestHandler = async (req, res) => {
addPls: addPls.length,
updatedPls: updateNum,
});
return null;
return;
} catch (error) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("updateUser", { error });
return null;
return;
}
};
@ -213,7 +213,7 @@ const fetchUser: RequestHandler = async (req, res) => {
try {
// if (randomBool(0.5)) {
// res.status(404).send({ message: "Not Found" });
// return null;
// return;
// }
if (!req.session.user)
throw new ReferenceError("session does not have user object");
@ -243,11 +243,11 @@ const fetchUser: RequestHandler = async (req, res) => {
pls: currentPlaylists.length,
links: currentLinks.length,
});
return null;
return;
} catch (error) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("fetchUser", { error });
return null;
return;
}
};
@ -268,12 +268,12 @@ const createLink: RequestHandler = async (req, res) => {
if (fromPl.type !== "playlist" || toPl.type !== "playlist") {
res.status(400).send({ message: "Links must be playlist links!" });
logger.debug("non-playlist link provided");
return null;
return;
}
} catch (error) {
res.status(400).send({ message: "Could not parse link" });
logger.info("parseSpotifyLink", { error });
return null;
return;
}
const playlists = await Playlists.findAll({
@ -287,7 +287,7 @@ const createLink: RequestHandler = async (req, res) => {
if (![fromPl, toPl].every((pl) => playlistIDs.includes(pl.id))) {
res.status(404).send({ message: "Unknown playlists, resync first." });
logger.debug("unknown playlists, resync");
return null;
return;
}
// check if exists
@ -299,7 +299,7 @@ const createLink: RequestHandler = async (req, res) => {
if (existingLink) {
res.status(409).send({ message: "Link already exists!" });
logger.debug("link already exists");
return null;
return;
}
const allLinks = await Links.findAll({
@ -318,7 +318,7 @@ const createLink: RequestHandler = async (req, res) => {
.status(400)
.send({ message: "The link cannot cause a cycle in the graph." });
logger.debug("potential cycle detected");
return null;
return;
}
const newLink = await Links.create({
@ -329,16 +329,16 @@ const createLink: RequestHandler = async (req, res) => {
if (!newLink) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("Could not create link");
return null;
return;
}
res.status(201).send({ message: "Created link." });
logger.debug("Created link");
return null;
return;
} catch (error) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("createLink", { error });
return null;
return;
}
};
@ -358,12 +358,12 @@ const removeLink: RequestHandler = async (req, res) => {
if (fromPl.type !== "playlist" || toPl.type !== "playlist") {
res.status(400).send({ message: "Links must be playlist links!" });
logger.debug("non-playlist link provided");
return null;
return;
}
} catch (error) {
res.status(400).send({ message: "Could not parse link" });
logger.info("parseSpotifyLink", { error });
return null;
return;
}
// check if exists
@ -375,7 +375,7 @@ const removeLink: RequestHandler = async (req, res) => {
if (!existingLink) {
res.status(409).send({ message: "Link does not exist!" });
logger.debug("link does not exist");
return null;
return;
}
const removedLink = await Links.destroy({
@ -386,16 +386,16 @@ const removeLink: RequestHandler = async (req, res) => {
if (!removedLink) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("Could not remove link");
return null;
return;
}
res.status(200).send({ message: "Deleted link." });
logger.debug("Deleted link");
return null;
return;
} catch (error) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("removeLink", { error });
return null;
return;
}
};
@ -409,7 +409,7 @@ interface _GetPlaylistTracks {
}
const _getPlaylistTracks: (
opts: _GetPlaylistTracksArgs
) => Promise<_GetPlaylistTracks | null> = async ({
) => Promise<_GetPlaylistTracks | void> = async ({
res,
authHeaders,
playlistID,
@ -421,7 +421,7 @@ const _getPlaylistTracks: (
initialFields: "snapshot_id",
playlistID,
});
if (!snapshotResp) return null;
if (!snapshotResp) return;
const currentSnapshotID = snapshotResp.data.snapshot_id;
@ -444,7 +444,7 @@ const _getPlaylistTracks: (
initialFields: firstPageFields.join(),
playlistID,
});
if (!firstResp) return null;
if (!firstResp) return;
const firstRespData = firstResp.data;
const pl: _GetPlaylistTracks = {
@ -472,7 +472,7 @@ const _getPlaylistTracks: (
res,
nextURL,
});
if (!resp) return null;
if (!resp) return;
const nextData = resp.data;
pl.tracks.push(
@ -555,12 +555,12 @@ const populateSingleLink: RequestHandler = async (req, res) => {
if (fromPl.type !== "playlist" || toPl.type !== "playlist") {
res.status(400).send({ message: "Link is not a playlist" });
logger.debug("non-playlist link provided", { link });
return null;
return;
}
} catch (error) {
res.status(400).send({ message: "Could not parse link" });
logger.info("parseSpotifyLink", { error });
return null;
return;
}
// check if exists
@ -572,7 +572,7 @@ const populateSingleLink: RequestHandler = async (req, res) => {
if (!existingLink) {
res.status(409).send({ message: "Link does not exist!" });
logger.debug("link does not exist", { link });
return null;
return;
}
const editableResp = await checkPlaylistEditable({
@ -584,7 +584,7 @@ const populateSingleLink: RequestHandler = async (req, res) => {
if (!editableResp.status) {
res.status(403).send({ message: editableResp.message });
logger.debug(editableResp.message, { editableResp });
return null;
return;
}
const fromTracks = await _getPlaylistTracks({
@ -592,13 +592,13 @@ const populateSingleLink: RequestHandler = async (req, res) => {
authHeaders,
playlistID: fromPl.id,
});
if (!fromTracks) return null;
if (!fromTracks) return;
const toTracks = await _getPlaylistTracks({
res,
authHeaders,
playlistID: toPl.id,
});
if (!toTracks) return null;
if (!toTracks) return;
const { missing, localNum } = _populateSingleLinkCore({
from: fromTracks.tracks,
@ -630,11 +630,11 @@ const populateSingleLink: RequestHandler = async (req, res) => {
res.status(200).send({ message, toAddNum, addedNum, localNum });
logger.debug(message, { toAddNum, addedNum, localNum });
return null;
return;
} catch (error) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("populateSingleLink", { error });
return null;
return;
}
};
@ -654,12 +654,12 @@ const populateChain: RequestHandler = async (req, res) => {
if (rootPl.type !== "playlist") {
res.status(400).send({ message: "Link is not a playlist" });
logger.debug("non-playlist link provided");
return null;
return;
}
} catch (error) {
res.status(400).send({ message: "Could not parse link" });
logger.info("parseSpotifyLink", { error });
return null;
return;
}
const playlists = await Playlists.findAll({
@ -689,7 +689,7 @@ const populateChain: RequestHandler = async (req, res) => {
});
})
);
if (res.headersSent) return null; // error, resp sent and logged in singleRequest
if (res.headersSent) return; // error, resp sent and logged in singleRequest
// else, respond with the non-editable playlists
const nonEditablePlaylists = editableStatuses.filter(
(statusObj) => statusObj.status === false
@ -700,7 +700,7 @@ const populateChain: RequestHandler = async (req, res) => {
nonEditablePlaylists.map((pl) => pl.error?.playlistName).join(", ");
res.status(403).send({ message });
logger.debug(message, { nonEditablePlaylists });
return null;
return;
}
const affectedPlaylistsTracks = await Promise.all(
@ -708,14 +708,14 @@ const populateChain: RequestHandler = async (req, res) => {
return _getPlaylistTracks({ res, authHeaders, playlistID: pl });
})
);
if (affectedPlaylistsTracks.some((plTracks) => !plTracks)) return null;
if (affectedPlaylistsTracks.some((plTracks) => !plTracks)) return;
const rootTracks = await _getPlaylistTracks({
res,
authHeaders,
playlistID: rootPl.id,
});
if (!rootTracks) return null;
if (!rootTracks) return;
const populateData = affectedPlaylistsTracks.map((plTracks) => {
return _populateSingleLinkCore({
@ -775,11 +775,11 @@ const populateChain: RequestHandler = async (req, res) => {
res.status(200).send({ message, ...reducedResult });
logger.debug(message, { ...reducedResult });
return null;
return;
} catch (error) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("populateChain", { error });
return null;
return;
}
};
@ -834,12 +834,12 @@ const pruneSingleLink: RequestHandler = async (req, res) => {
if (fromPl.type !== "playlist" || toPl.type !== "playlist") {
res.status(400).send({ message: "Link is not a playlist" });
logger.debug("non-playlist link provided");
return null;
return;
}
} catch (error: any) {
res.status(400).send({ message: error.message });
logger.info("parseSpotifyLink", { error });
return null;
return;
}
// check if exists
@ -851,7 +851,7 @@ const pruneSingleLink: RequestHandler = async (req, res) => {
if (!existingLink) {
res.status(409).send({ message: "Link does not exist!" });
logger.warn("link does not exist", { link });
return null;
return;
}
const editableResp = await checkPlaylistEditable({
@ -863,7 +863,7 @@ const pruneSingleLink: RequestHandler = async (req, res) => {
if (!editableResp.status) {
res.status(403).send({ message: editableResp.message });
logger.debug(editableResp.message, { editableResp });
return null;
return;
}
const fromTracks = await _getPlaylistTracks({
@ -871,14 +871,14 @@ const pruneSingleLink: RequestHandler = async (req, res) => {
authHeaders,
playlistID: fromPl.id,
});
if (!fromTracks) return null;
if (!fromTracks) return;
const toTracks = await _getPlaylistTracks({
res,
authHeaders,
playlistID: toPl.id,
});
if (!toTracks) return null;
if (!toTracks) return;
const { missingPositions } = _pruneSingleLinkCore({
from: fromTracks.tracks,
@ -918,11 +918,11 @@ const pruneSingleLink: RequestHandler = async (req, res) => {
res.status(200).send({ message, toDelNum, deletedNum });
logger.debug(message, { toDelNum, deletedNum });
return null;
return;
} catch (error) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("pruneSingleLink", { error });
return null;
return;
}
};
@ -942,12 +942,12 @@ const pruneChain: RequestHandler = async (req, res) => {
if (rootPl.type !== "playlist") {
res.status(400).send({ message: "Link is not a playlist" });
logger.debug("non-playlist link provided");
return null;
return;
}
} catch (error) {
res.status(400).send({ message: "Could not parse link" });
logger.info("parseSpotifyLink", { error });
return null;
return;
}
const playlists = await Playlists.findAll({
@ -977,7 +977,7 @@ const pruneChain: RequestHandler = async (req, res) => {
});
})
);
if (res.headersSent) return null; // error, resp sent and logged in singleRequest
if (res.headersSent) return; // error, resp sent and logged in singleRequest
// else, respond with the non-editable playlists
const nonEditablePlaylists = editableStatuses.filter(
(statusObj) => statusObj.status === false
@ -988,7 +988,7 @@ const pruneChain: RequestHandler = async (req, res) => {
nonEditablePlaylists.map((pl) => pl.error?.playlistName).join(", ");
res.status(403).send({ message });
logger.debug(message, { nonEditablePlaylists });
return null;
return;
}
const rootTracks = await _getPlaylistTracks({
@ -996,14 +996,14 @@ const pruneChain: RequestHandler = async (req, res) => {
authHeaders,
playlistID: rootPl.id,
});
if (!rootTracks) return null;
if (!rootTracks) return;
const affectedPlaylistsTracks = await Promise.all(
affectedPlaylists.map((pl) => {
return _getPlaylistTracks({ res, authHeaders, playlistID: pl });
})
);
if (affectedPlaylistsTracks.some((plTracks) => !plTracks)) return null;
if (affectedPlaylistsTracks.some((plTracks) => !plTracks)) return;
const pruneData = affectedPlaylistsTracks.map((plTracks) => {
return _pruneSingleLinkCore({
@ -1060,11 +1060,11 @@ const pruneChain: RequestHandler = async (req, res) => {
res.status(200).send({ message, ...reducedResult });
logger.debug(message, { ...reducedResult });
return null;
return;
} catch (error) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("pruneChain", { error });
return null;
return;
}
};

View File

@ -24,7 +24,7 @@ const fetchUserPlaylists: RequestHandler = async (req, res) => {
res,
authHeaders,
});
if (!resp) return null;
if (!resp) return;
const userPlaylists: Pick<
Pagination<SimplifiedPlaylistObject>,
@ -41,7 +41,7 @@ const fetchUserPlaylists: RequestHandler = async (req, res) => {
res,
nextURL,
});
if (!resp) return null;
if (!resp) return;
const nextData = resp.data;
userPlaylists.items.push(...nextData.items);
@ -50,11 +50,11 @@ const fetchUserPlaylists: RequestHandler = async (req, res) => {
res.status(200).send(userPlaylists);
logger.debug("Fetched user playlists", { num: userPlaylists.total });
return null;
return;
} catch (error) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("fetchUserPlaylists", { error });
return null;
return;
}
};

View File

@ -84,7 +84,7 @@ app.use(express.static(import.meta.dirname + "/static"));
// Healthcheck
app.use("/health", (_req, res) => {
res.status(200).send({ message: "OK" });
return null;
return;
});
app.use("/auth-health", isAuthenticated, async (req, res) => {
try {
@ -92,13 +92,13 @@ app.use("/auth-health", isAuthenticated, async (req, res) => {
if (!authHeaders)
throw new ReferenceError("session does not have auth headers");
const { resp } = await getCurrentUsersProfile({ authHeaders, res });
if (!resp) return null;
if (!resp) return;
res.status(200).send({ message: "OK" });
return null;
return;
} catch (error) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("authHealthCheck", { error });
return null;
return;
}
});
@ -116,7 +116,7 @@ app.use((req, res) => {
'Guess the <a href="https://github.com/20kaushik02/spotify-manager">cat\'s</a> out of the bag!'
);
logger.info("404", { url: req.url });
return null;
return;
});
const port = process.env["SPOTMGR_PORT"] || 5000;

View File

@ -15,14 +15,14 @@ const isAuthenticated: RequestHandler = (req, res, next) => {
if (Object.keys(error).length) {
res.status(500).send({ message: "Internal Server Error" });
logger.error("session.destroy", { error });
return null;
return;
} else {
res.clearCookie(sessionName);
res.status(401).send({ message: "Unauthorized" });
logger.debug("Session invalid, destroyed.", {
sessionID: delSession.id,
});
return null;
return;
}
});
}

921
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -20,34 +20,34 @@
},
"homepage": "https://api.spotify-manager.knravish.me",
"dependencies": {
"axios": "^1.8.4",
"axios": "^1.9.0",
"bottleneck": "^2.19.5",
"connect-redis": "^8.0.3",
"connect-redis": "^8.1.0",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"express": "^4.21.2",
"express": "^5.1.0",
"express-session": "^1.18.1",
"express-validator": "^7.2.1",
"helmet": "^8.1.0",
"multer": "^1.4.5-lts.2",
"pg": "^8.15.1",
"redis": "^4.7.0",
"multer": "^2.0.0",
"pg": "^8.16.0",
"redis": "^5.1.1",
"reflect-metadata": "^0.2.2",
"sequelize": "^6.37.7",
"sequelize-cli": "^6.6.2",
"sequelize-cli": "^6.6.3",
"sequelize-typescript": "^2.1.6",
"serializr": "^3.0.4",
"serializr": "^3.0.5",
"winston": "^3.17.0"
},
"devDependencies": {
"@types/cookie-parser": "^1.4.8",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/cors": "^2.8.8",
"@types/express": "^5.0.2",
"@types/express-session": "^1.18.1",
"@types/multer": "^1.4.12",
"@types/node": "^22.13.11",
"@types/node": "^22.15.29",
"@types/sequelize": "^4.28.20",
"@types/validator": "^13.12.2",
"typescript": "^5.8.2"
"@types/validator": "^13.15.1",
"typescript": "^5.8.3"
}
}

View File

@ -32,7 +32,7 @@ const validate: RequestHandler = (req, res, next) => {
errors: extractedErrors,
});
logger.warn("invalid request", { extractedErrors });
return null;
return;
};
export { validate };