mirror of
https://github.com/20kaushik02/spotify-manager.git
synced 2025-12-06 07:54:07 +00:00
back again...
bit of validation, some fixes, some auth corrections scrapped graph db stuff some misc. stuff, check the diff if you want bruh
This commit is contained in:
parent
2225f5db49
commit
5803c997b2
1
.gitignore
vendored
1
.gitignore
vendored
@ -71,6 +71,7 @@ typings/
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.development
|
||||
.env.production
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
@ -68,7 +68,7 @@ const callback = async (req, res) => {
|
||||
logger.info('New login.');
|
||||
req.session.accessToken = response.data.access_token;
|
||||
req.session.refreshToken = response.data.refresh_token;
|
||||
// note that session does not expire; so infinite refresh, just default access token expiration
|
||||
req.session.cookie.maxAge = 7776000000 // 90 days, arbitrary
|
||||
|
||||
req.session.save((err) => {
|
||||
if (err) {
|
||||
@ -110,6 +110,7 @@ const refresh = async (req, res) => {
|
||||
if (response.status === 200) {
|
||||
req.session.accessToken = response.data.access_token;
|
||||
req.session.refreshToken = response.data.refresh_token ?? req.session.refreshToken; // refresh token rotation
|
||||
req.session.cookie.maxAge = 7776000000 // 90 days, arbitrary
|
||||
|
||||
logger.info(`Access token refreshed${(response.data.refresh_token !== null) ? ' and refresh token updated' : ''}.`);
|
||||
return res.status(200).send({
|
||||
@ -133,7 +134,7 @@ const refresh = async (req, res) => {
|
||||
const logout = async (req, res) => {
|
||||
try {
|
||||
const delSession = req.session.destroy((err) => {
|
||||
if (Object.keys(err).length) {
|
||||
if (Object.keys(err).length) { // err is empty obj if no error
|
||||
logger.error("Error while logging out", { err });
|
||||
return res.sendStatus(500);
|
||||
} else {
|
||||
|
||||
@ -26,8 +26,9 @@ const getUserPlaylists = async (req, res) => {
|
||||
}
|
||||
);
|
||||
|
||||
if (response.status === 401)
|
||||
if (response.status === 401) {
|
||||
return res.status(401).send(response.data);
|
||||
}
|
||||
|
||||
/** @type {typedefs.SimplifiedPlaylist[]} */
|
||||
playlists.items = response.data.items.map((playlist) => {
|
||||
|
||||
42
index.js
42
index.js
@ -1,5 +1,6 @@
|
||||
require('dotenv-flow').config();
|
||||
|
||||
const util = require('util');
|
||||
const express = require('express');
|
||||
const session = require("express-session");
|
||||
|
||||
@ -9,7 +10,6 @@ const helmet = require("helmet");
|
||||
|
||||
const redis = require('redis');
|
||||
const RedisStore = require("connect-redis").default;
|
||||
const neo4j = require('neo4j-driver');
|
||||
|
||||
const logger = require("./utils/logger")(module);
|
||||
|
||||
@ -35,24 +35,6 @@ redisClient.connect()
|
||||
|
||||
const redisStore = new RedisStore({ client: redisClient });
|
||||
|
||||
// Configure Neo4j driver and connect
|
||||
const neo4jDriver = neo4j.driver(
|
||||
process.env.NEO4J_URL,
|
||||
neo4j.auth.basic(process.env.NEO4J_USER, process.env.NEO4J_PASSWD)
|
||||
);
|
||||
const neo4jSession = neo4jDriver.session();
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await neo4jSession.run('MATCH () RETURN 1 LIMIT 1');
|
||||
app.locals.neodb = neo4jSession;
|
||||
logger.info("Connected to Neo4j graph DB");
|
||||
} catch (error) {
|
||||
logger.error("Neo4j connection error", { error });
|
||||
cleanupFunc();
|
||||
}
|
||||
})();
|
||||
|
||||
// Configure session middleware
|
||||
app.use(session({
|
||||
store: redisStore,
|
||||
@ -100,16 +82,18 @@ const server = app.listen(port, () => {
|
||||
logger.info(`App Listening on port ${port}`);
|
||||
});
|
||||
|
||||
const cleanupFunc = async (signal) => {
|
||||
if (neo4jSession) await neo4jSession.close();
|
||||
if (neo4jDriver) await neo4jDriver.close();
|
||||
logger.info('Neo4j connection closed');
|
||||
if (redisClient) await redisClient.disconnect();
|
||||
logger.info('Redis client disconnected');
|
||||
server.close(() => {
|
||||
logger.info('HTTP server closed');
|
||||
const cleanupFunc = (signal) => {
|
||||
Promise.allSettled([
|
||||
redisClient.disconnect,
|
||||
util.promisify(server.close),
|
||||
]).then(() => {
|
||||
if (signal)
|
||||
logger.info(`Caught ${signal} signal`);
|
||||
logger.info("Cleaned up, exiting...");
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
process.on('SIGINT', cleanupFunc);
|
||||
process.on('SIGTERM', cleanupFunc);
|
||||
['SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGTERM', 'SIGUSR1', 'SIGUSR2'].forEach((signal) => {
|
||||
process.on(signal, () => cleanupFunc(signal));
|
||||
});
|
||||
|
||||
1983
package-lock.json
generated
1983
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -27,7 +27,6 @@
|
||||
"express-session": "^1.17.3",
|
||||
"express-validator": "^7.0.1",
|
||||
"helmet": "^7.0.0",
|
||||
"neo4j-driver": "^5.15.0",
|
||||
"redis": "^4.6.10",
|
||||
"winston": "^3.10.0"
|
||||
},
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
const router = require('express').Router();
|
||||
|
||||
const { login, callback, refresh, logout } = require('../controllers/auth');
|
||||
const { isAuthenticated } = require('../middleware/authCheck');
|
||||
const validator = require("../validators");
|
||||
|
||||
router.get(
|
||||
@ -15,7 +16,7 @@ router.get(
|
||||
|
||||
router.get(
|
||||
"/refresh",
|
||||
validator.validate,
|
||||
isAuthenticated,
|
||||
refresh
|
||||
)
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ const router = require('express').Router();
|
||||
|
||||
const { getUserPlaylists, getPlaylistDetails, } = require('../controllers/playlists');
|
||||
const { isAuthenticated } = require('../middleware/authCheck');
|
||||
const { getPlaylistDetailsValidator } = require('../validators/playlists');
|
||||
const validator = require("../validators");
|
||||
|
||||
router.get(
|
||||
@ -13,6 +14,7 @@ router.get(
|
||||
router.get(
|
||||
"/details",
|
||||
isAuthenticated,
|
||||
getPlaylistDetailsValidator,
|
||||
validator.validate,
|
||||
getPlaylistDetails
|
||||
);
|
||||
|
||||
@ -7,8 +7,6 @@
|
||||
*
|
||||
* @typedef {import('winston').Logger} Logger
|
||||
*
|
||||
* @typedef {import('neo4j-driver').Session} Neo4jSession
|
||||
*
|
||||
* @typedef {{
|
||||
* display_name: string,
|
||||
* uri: string,
|
||||
|
||||
@ -34,7 +34,7 @@ axiosInstance.interceptors.response.use(
|
||||
if (error.response) {
|
||||
// Server has responded
|
||||
logger.error(
|
||||
"API: Error", {
|
||||
"Spotify API: Error", {
|
||||
response: {
|
||||
status: error.response.status,
|
||||
statusText: error.response.statusText,
|
||||
@ -45,7 +45,7 @@ axiosInstance.interceptors.response.use(
|
||||
} else if (error.request) {
|
||||
// The request was made but no response was received
|
||||
logger.error(
|
||||
"API: No response", {
|
||||
"Spotify API: No response", {
|
||||
request: {
|
||||
url: error.request?.url,
|
||||
params: error.request?.params,
|
||||
@ -54,7 +54,7 @@ axiosInstance.interceptors.response.use(
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
logger.error(
|
||||
"API: Request error", {
|
||||
"Spotify API: Request error", {
|
||||
error: {
|
||||
message: error.message,
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
const path = require("path");
|
||||
|
||||
const { createLogger, transports, config, format } = require('winston');
|
||||
const { combine, label, timestamp, printf } = format;
|
||||
const { combine, label, timestamp, printf, errors } = format;
|
||||
|
||||
const typedefs = require("../typedefs");
|
||||
|
||||
@ -12,21 +12,26 @@ const getLabel = (callingModule) => {
|
||||
|
||||
const logMetaReplacer = (key, value) => {
|
||||
if (key === "error") {
|
||||
return value.name + ': ' + value.message;
|
||||
return {
|
||||
name: value.name,
|
||||
message: value.message,
|
||||
stack: value.stack
|
||||
};
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
const metaFormat = (meta) => {
|
||||
if (Object.keys(meta).length > 0)
|
||||
return '\n' + JSON.stringify(meta, logMetaReplacer) + '\n';
|
||||
return '\n' + JSON.stringify(meta, logMetaReplacer, "\t") + '\n';
|
||||
return '\n';
|
||||
}
|
||||
|
||||
const logFormat = printf(({ level, message, label, timestamp, ...meta }) => {
|
||||
if (meta.error) {
|
||||
for (const key in meta.error) {
|
||||
if (typeof key !== "symbol" && key !== "message" && key !== "name") {
|
||||
const allowedErrorKeys = ["name", "message", "stack"]
|
||||
if (typeof key !== "symbol" && !allowedErrorKeys.includes(key)) {
|
||||
delete meta.error[key]
|
||||
}
|
||||
}
|
||||
@ -43,6 +48,7 @@ const logger = (callingModule) => {
|
||||
return createLogger({
|
||||
levels: config.npm.levels,
|
||||
format: combine(
|
||||
errors({ stack: true }),
|
||||
label({ label: getLabel(callingModule) }),
|
||||
timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
|
||||
logFormat,
|
||||
@ -51,7 +57,7 @@ const logger = (callingModule) => {
|
||||
process.env.NODE_ENV !== 'production' ?
|
||||
new transports.Console() :
|
||||
new transports.Console(),
|
||||
new transports.File({ filename: __dirname + '/../logs/common.log' }),
|
||||
new transports.File({ filename: __dirname + '/../logs/common.log' }),
|
||||
new transports.File({ filename: __dirname + '/../logs/error.log', level: 'error' }),
|
||||
]
|
||||
});
|
||||
|
||||
@ -2,6 +2,7 @@ const { validationResult } = require("express-validator");
|
||||
|
||||
const typedefs = require("../typedefs");
|
||||
const { getNestedValuesString } = require("../utils/jsonTransformer");
|
||||
const logger = require("../utils/logger")(module);
|
||||
|
||||
/**
|
||||
* Refer: https://stackoverflow.com/questions/58848625/access-messages-in-express-validator
|
||||
@ -17,7 +18,7 @@ const validate = (req, res, next) => {
|
||||
}
|
||||
const extractedErrors = []
|
||||
errors.array().map(err => extractedErrors.push({
|
||||
[err.param]: err.msg
|
||||
[err.path]: err.msg
|
||||
}));
|
||||
|
||||
return res.status(400).json({
|
||||
|
||||
23
validators/playlists.js
Normal file
23
validators/playlists.js
Normal file
@ -0,0 +1,23 @@
|
||||
const { body, header, param, query } = require("express-validator");
|
||||
|
||||
const typedefs = require("../typedefs");
|
||||
|
||||
/**
|
||||
* @param {typedefs.Req} req
|
||||
* @param {typedefs.Res} res
|
||||
* @param {typedefs.Next} next
|
||||
*/
|
||||
const getPlaylistDetailsValidator = async (req, res, next) => {
|
||||
await query("playlist_id")
|
||||
.notEmpty()
|
||||
.withMessage("playlist_id not defined in query")
|
||||
.isAlphanumeric()
|
||||
.withMessage("playlist_id must be alphanumeric (base-62)")
|
||||
.run(req);
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getPlaylistDetailsValidator,
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user