mirror of
https://github.com/20kaushik02/spotify-manager.git
synced 2025-12-06 10: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
|
# dotenv environment variables file
|
||||||
.env
|
.env
|
||||||
.env.development
|
.env.development
|
||||||
|
.env.production
|
||||||
.env.test
|
.env.test
|
||||||
|
|
||||||
# parcel-bundler cache (https://parceljs.org/)
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
|||||||
@ -68,7 +68,7 @@ const callback = async (req, res) => {
|
|||||||
logger.info('New login.');
|
logger.info('New login.');
|
||||||
req.session.accessToken = response.data.access_token;
|
req.session.accessToken = response.data.access_token;
|
||||||
req.session.refreshToken = response.data.refresh_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) => {
|
req.session.save((err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -110,6 +110,7 @@ const refresh = async (req, res) => {
|
|||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
req.session.accessToken = response.data.access_token;
|
req.session.accessToken = response.data.access_token;
|
||||||
req.session.refreshToken = response.data.refresh_token ?? req.session.refreshToken; // refresh token rotation
|
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' : ''}.`);
|
logger.info(`Access token refreshed${(response.data.refresh_token !== null) ? ' and refresh token updated' : ''}.`);
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
@ -133,7 +134,7 @@ const refresh = async (req, res) => {
|
|||||||
const logout = async (req, res) => {
|
const logout = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const delSession = req.session.destroy((err) => {
|
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 });
|
logger.error("Error while logging out", { err });
|
||||||
return res.sendStatus(500);
|
return res.sendStatus(500);
|
||||||
} else {
|
} 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);
|
return res.status(401).send(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
/** @type {typedefs.SimplifiedPlaylist[]} */
|
/** @type {typedefs.SimplifiedPlaylist[]} */
|
||||||
playlists.items = response.data.items.map((playlist) => {
|
playlists.items = response.data.items.map((playlist) => {
|
||||||
|
|||||||
42
index.js
42
index.js
@ -1,5 +1,6 @@
|
|||||||
require('dotenv-flow').config();
|
require('dotenv-flow').config();
|
||||||
|
|
||||||
|
const util = require('util');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const session = require("express-session");
|
const session = require("express-session");
|
||||||
|
|
||||||
@ -9,7 +10,6 @@ const helmet = require("helmet");
|
|||||||
|
|
||||||
const redis = require('redis');
|
const redis = require('redis');
|
||||||
const RedisStore = require("connect-redis").default;
|
const RedisStore = require("connect-redis").default;
|
||||||
const neo4j = require('neo4j-driver');
|
|
||||||
|
|
||||||
const logger = require("./utils/logger")(module);
|
const logger = require("./utils/logger")(module);
|
||||||
|
|
||||||
@ -35,24 +35,6 @@ redisClient.connect()
|
|||||||
|
|
||||||
const redisStore = new RedisStore({ client: redisClient });
|
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
|
// Configure session middleware
|
||||||
app.use(session({
|
app.use(session({
|
||||||
store: redisStore,
|
store: redisStore,
|
||||||
@ -100,16 +82,18 @@ const server = app.listen(port, () => {
|
|||||||
logger.info(`App Listening on port ${port}`);
|
logger.info(`App Listening on port ${port}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
const cleanupFunc = async (signal) => {
|
const cleanupFunc = (signal) => {
|
||||||
if (neo4jSession) await neo4jSession.close();
|
Promise.allSettled([
|
||||||
if (neo4jDriver) await neo4jDriver.close();
|
redisClient.disconnect,
|
||||||
logger.info('Neo4j connection closed');
|
util.promisify(server.close),
|
||||||
if (redisClient) await redisClient.disconnect();
|
]).then(() => {
|
||||||
logger.info('Redis client disconnected');
|
if (signal)
|
||||||
server.close(() => {
|
logger.info(`Caught ${signal} signal`);
|
||||||
logger.info('HTTP server closed');
|
logger.info("Cleaned up, exiting...");
|
||||||
|
process.exit(0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
process.on('SIGINT', cleanupFunc);
|
['SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGTERM', 'SIGUSR1', 'SIGUSR2'].forEach((signal) => {
|
||||||
process.on('SIGTERM', cleanupFunc);
|
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-session": "^1.17.3",
|
||||||
"express-validator": "^7.0.1",
|
"express-validator": "^7.0.1",
|
||||||
"helmet": "^7.0.0",
|
"helmet": "^7.0.0",
|
||||||
"neo4j-driver": "^5.15.0",
|
|
||||||
"redis": "^4.6.10",
|
"redis": "^4.6.10",
|
||||||
"winston": "^3.10.0"
|
"winston": "^3.10.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
const router = require('express').Router();
|
const router = require('express').Router();
|
||||||
|
|
||||||
const { login, callback, refresh, logout } = require('../controllers/auth');
|
const { login, callback, refresh, logout } = require('../controllers/auth');
|
||||||
|
const { isAuthenticated } = require('../middleware/authCheck');
|
||||||
const validator = require("../validators");
|
const validator = require("../validators");
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
@ -15,7 +16,7 @@ router.get(
|
|||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/refresh",
|
"/refresh",
|
||||||
validator.validate,
|
isAuthenticated,
|
||||||
refresh
|
refresh
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ const router = require('express').Router();
|
|||||||
|
|
||||||
const { getUserPlaylists, getPlaylistDetails, } = require('../controllers/playlists');
|
const { getUserPlaylists, getPlaylistDetails, } = require('../controllers/playlists');
|
||||||
const { isAuthenticated } = require('../middleware/authCheck');
|
const { isAuthenticated } = require('../middleware/authCheck');
|
||||||
|
const { getPlaylistDetailsValidator } = require('../validators/playlists');
|
||||||
const validator = require("../validators");
|
const validator = require("../validators");
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
@ -13,6 +14,7 @@ router.get(
|
|||||||
router.get(
|
router.get(
|
||||||
"/details",
|
"/details",
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
|
getPlaylistDetailsValidator,
|
||||||
validator.validate,
|
validator.validate,
|
||||||
getPlaylistDetails
|
getPlaylistDetails
|
||||||
);
|
);
|
||||||
|
|||||||
@ -7,8 +7,6 @@
|
|||||||
*
|
*
|
||||||
* @typedef {import('winston').Logger} Logger
|
* @typedef {import('winston').Logger} Logger
|
||||||
*
|
*
|
||||||
* @typedef {import('neo4j-driver').Session} Neo4jSession
|
|
||||||
*
|
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* display_name: string,
|
* display_name: string,
|
||||||
* uri: string,
|
* uri: string,
|
||||||
|
|||||||
@ -34,7 +34,7 @@ axiosInstance.interceptors.response.use(
|
|||||||
if (error.response) {
|
if (error.response) {
|
||||||
// Server has responded
|
// Server has responded
|
||||||
logger.error(
|
logger.error(
|
||||||
"API: Error", {
|
"Spotify API: Error", {
|
||||||
response: {
|
response: {
|
||||||
status: error.response.status,
|
status: error.response.status,
|
||||||
statusText: error.response.statusText,
|
statusText: error.response.statusText,
|
||||||
@ -45,7 +45,7 @@ axiosInstance.interceptors.response.use(
|
|||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
// The request was made but no response was received
|
// The request was made but no response was received
|
||||||
logger.error(
|
logger.error(
|
||||||
"API: No response", {
|
"Spotify API: No response", {
|
||||||
request: {
|
request: {
|
||||||
url: error.request?.url,
|
url: error.request?.url,
|
||||||
params: error.request?.params,
|
params: error.request?.params,
|
||||||
@ -54,7 +54,7 @@ axiosInstance.interceptors.response.use(
|
|||||||
} else {
|
} else {
|
||||||
// Something happened in setting up the request that triggered an Error
|
// Something happened in setting up the request that triggered an Error
|
||||||
logger.error(
|
logger.error(
|
||||||
"API: Request error", {
|
"Spotify API: Request error", {
|
||||||
error: {
|
error: {
|
||||||
message: error.message,
|
message: error.message,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
const { createLogger, transports, config, format } = require('winston');
|
const { createLogger, transports, config, format } = require('winston');
|
||||||
const { combine, label, timestamp, printf } = format;
|
const { combine, label, timestamp, printf, errors } = format;
|
||||||
|
|
||||||
const typedefs = require("../typedefs");
|
const typedefs = require("../typedefs");
|
||||||
|
|
||||||
@ -12,21 +12,26 @@ const getLabel = (callingModule) => {
|
|||||||
|
|
||||||
const logMetaReplacer = (key, value) => {
|
const logMetaReplacer = (key, value) => {
|
||||||
if (key === "error") {
|
if (key === "error") {
|
||||||
return value.name + ': ' + value.message;
|
return {
|
||||||
|
name: value.name,
|
||||||
|
message: value.message,
|
||||||
|
stack: value.stack
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const metaFormat = (meta) => {
|
const metaFormat = (meta) => {
|
||||||
if (Object.keys(meta).length > 0)
|
if (Object.keys(meta).length > 0)
|
||||||
return '\n' + JSON.stringify(meta, logMetaReplacer) + '\n';
|
return '\n' + JSON.stringify(meta, logMetaReplacer, "\t") + '\n';
|
||||||
return '\n';
|
return '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
const logFormat = printf(({ level, message, label, timestamp, ...meta }) => {
|
const logFormat = printf(({ level, message, label, timestamp, ...meta }) => {
|
||||||
if (meta.error) {
|
if (meta.error) {
|
||||||
for (const key in 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]
|
delete meta.error[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,6 +48,7 @@ const logger = (callingModule) => {
|
|||||||
return createLogger({
|
return createLogger({
|
||||||
levels: config.npm.levels,
|
levels: config.npm.levels,
|
||||||
format: combine(
|
format: combine(
|
||||||
|
errors({ stack: true }),
|
||||||
label({ label: getLabel(callingModule) }),
|
label({ label: getLabel(callingModule) }),
|
||||||
timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
|
timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
|
||||||
logFormat,
|
logFormat,
|
||||||
@ -51,7 +57,7 @@ const logger = (callingModule) => {
|
|||||||
process.env.NODE_ENV !== 'production' ?
|
process.env.NODE_ENV !== 'production' ?
|
||||||
new transports.Console() :
|
new transports.Console() :
|
||||||
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' }),
|
new transports.File({ filename: __dirname + '/../logs/error.log', level: 'error' }),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,6 +2,7 @@ const { validationResult } = require("express-validator");
|
|||||||
|
|
||||||
const typedefs = require("../typedefs");
|
const typedefs = require("../typedefs");
|
||||||
const { getNestedValuesString } = require("../utils/jsonTransformer");
|
const { getNestedValuesString } = require("../utils/jsonTransformer");
|
||||||
|
const logger = require("../utils/logger")(module);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refer: https://stackoverflow.com/questions/58848625/access-messages-in-express-validator
|
* Refer: https://stackoverflow.com/questions/58848625/access-messages-in-express-validator
|
||||||
@ -17,7 +18,7 @@ const validate = (req, res, next) => {
|
|||||||
}
|
}
|
||||||
const extractedErrors = []
|
const extractedErrors = []
|
||||||
errors.array().map(err => extractedErrors.push({
|
errors.array().map(err => extractedErrors.push({
|
||||||
[err.param]: err.msg
|
[err.path]: err.msg
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return res.status(400).json({
|
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