ocd formatting, changed user object, retrieve user ID

This commit is contained in:
Kaushik Narayan R 2024-07-25 09:05:41 +05:30
parent 40cf0c2e2b
commit f067320a7f
21 changed files with 112 additions and 104 deletions

4
.env Normal file
View File

@ -0,0 +1,4 @@
CLIENT_ID = your_client_id_here
CLIENT_SECRET = your_client_secret_here
SESSION_SECRET = 'your_session_secret_string_here'
PORT = 9001

2
.env.development Normal file
View File

@ -0,0 +1,2 @@
REDIRECT_URI = http://localhost:9001/api/auth/callback
TRUST_PROXY=1

2
.env.production Normal file
View File

@ -0,0 +1,2 @@
REDIRECT_URI = https://domain.for.this.app/api/auth/callback
TRUST_PROXY=1

8
.gitignore vendored
View File

@ -69,10 +69,10 @@ typings/
.yarn-integrity .yarn-integrity
# dotenv environment variables file # dotenv environment variables file
.env .env.local
.env.development .env.development.local
.env.production .env.production.local
.env.test .env.test.local
# parcel-bundler cache (https://parceljs.org/) # parcel-bundler cache (https://parceljs.org/)
.cache .cache

View File

@ -1,2 +1,3 @@
# spotify-manager # spotify-manager
Personal Spotify playlist manager. Features inbound! Personal Spotify playlist manager. Features inbound!

View File

@ -17,6 +17,5 @@ const __validator_func = async (req, res, next) => {
} }
module.exports = { module.exports = {
__validator_func, __validator_func
} }

View File

@ -19,7 +19,7 @@ const scopes = {
ModifyPrivatePlaylists: 'playlist-modify-private', ModifyPrivatePlaylists: 'playlist-modify-private',
ControlRemotePlayback: 'app-remote-control', ControlRemotePlayback: 'app-remote-control',
ModifyLibrary: 'user-library-modify', ModifyLibrary: 'user-library-modify',
ViewLibrary: 'user-library-read', ViewLibrary: 'user-library-read'
}; };
module.exports = { module.exports = {

View File

@ -1,4 +1,4 @@
const { authInstance } = require("../utils/axios"); const { authInstance, axiosInstance } = require("../utils/axios");
const typedefs = require("../typedefs"); const typedefs = require("../typedefs");
const { scopes, stateKey, accountsAPIURL, sessionAgeInSeconds } = require('../constants'); const { scopes, stateKey, accountsAPIURL, sessionAgeInSeconds } = require('../constants');
@ -62,28 +62,36 @@ const callback = async (req, res) => {
const authPayload = (new URLSearchParams(authForm)).toString(); const authPayload = (new URLSearchParams(authForm)).toString();
const response = await authInstance.post('/api/token', authPayload); const tokenResponse = await authInstance.post('/api/token', authPayload);
if (response.status === 200) { if (tokenResponse.status === 200) {
logger.info('New login.'); logger.info('New login.');
req.session.accessToken = response.data.access_token; req.session.accessToken = tokenResponse.data.access_token;
req.session.refreshToken = response.data.refresh_token; req.session.refreshToken = tokenResponse.data.refresh_token;
req.session.cookie.maxAge = 7 * 24 * 60 * 60 * 1000 // 1 week req.session.cookie.maxAge = 7 * 24 * 60 * 60 * 1000 // 1 week
req.session.save((err) => {
if (err) {
logger.error("redis session save error", { sessionError: err })
throw err;
}
});
return res.status(200).send({
message: "Login successful",
});
} else { } else {
logger.error('login failed', { statusCode: response.status }); logger.error('login failed', { statusCode: tokenResponse.status });
res.status(response.status).send('Error: Login failed'); res.status(tokenResponse.status).send('Error: Login failed');
} }
const userResponse = await axiosInstance.get(
"/me",
{
headers: {
'Authorization': `Bearer ${req.session.accessToken}`
}
}
);
/** @type {typedefs.User} */
req.session.user = {
username: userResponse.data.display_name,
id: userResponse.data.id,
};
return res.status(200).send({
message: "Login successful",
});
} }
} catch (error) { } catch (error) {
logger.error('callback', { error }); logger.error('callback', { error });
@ -153,5 +161,5 @@ module.exports = {
login, login,
callback, callback,
refresh, refresh,
logout, logout
}; };

View File

@ -14,7 +14,7 @@ const getUserPlaylists = async (req, res) => {
// get first 50 // get first 50
const response = await axiosInstance.get( const response = await axiosInstance.get(
"/me/playlists", `/users/${req.session.user.id}/playlists`,
{ {
params: { params: {
offset: 0, offset: 0,
@ -26,7 +26,7 @@ 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);
} }
@ -34,9 +34,7 @@ const getUserPlaylists = async (req, res) => {
playlists.items = response.data.items.map((playlist) => { playlists.items = response.data.items.map((playlist) => {
return { return {
name: playlist.name, name: playlist.name,
description: playlist.description, id: playlist.id
owner_name: playlist.owner.display_name,
id: playlist.id,
} }
}); });
@ -60,9 +58,7 @@ const getUserPlaylists = async (req, res) => {
...nextResponse.data.items.map((playlist) => { ...nextResponse.data.items.map((playlist) => {
return { return {
name: playlist.name, name: playlist.name,
description: playlist.description, id: playlist.id
owner_name: playlist.owner.display_name,
id: playlist.id,
} }
}) })
); );
@ -70,6 +66,8 @@ const getUserPlaylists = async (req, res) => {
playlists.next = nextResponse.data.next; playlists.next = nextResponse.data.next;
} }
delete playlists.next;
return res.status(200).send(playlists); return res.status(200).send(playlists);
} catch (error) { } catch (error) {
logger.error('getUserPlaylists', { error }); logger.error('getUserPlaylists', { error });
@ -161,5 +159,5 @@ const getPlaylistDetails = async (req, res) => {
module.exports = { module.exports = {
getUserPlaylists, getUserPlaylists,
getPlaylistDetails, getPlaylistDetails
}; };

View File

@ -35,12 +35,7 @@ app.use(session({
} }
})); }));
// Configure CORS options app.use(cors());
const corsOptions = {
origin: [process.env.CORS_ORIGIN],
}
app.use(cors(corsOptions));
app.use(cookieParser()); app.use(cookieParser());
// Configure helmet // Configure helmet

View File

@ -26,5 +26,5 @@ const isAuthenticated = (req, res, next) => {
} }
module.exports = { module.exports = {
isAuthenticated, isAuthenticated
} }

View File

@ -18,10 +18,11 @@ router.get(
"/refresh", "/refresh",
isAuthenticated, isAuthenticated,
refresh refresh
) );
router.get( router.get(
"/logout", "/logout",
logout, logout
) );
module.exports = router; module.exports = router;

View File

@ -1,6 +1,6 @@
const router = require('express').Router(); 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 { getPlaylistDetailsValidator } = require('../validators/playlists');
const validator = require("../validators"); const validator = require("../validators");
@ -11,6 +11,7 @@ router.get(
validator.validate, validator.validate,
getUserPlaylists getUserPlaylists
); );
router.get( router.get(
"/details", "/details",
isAuthenticated, isAuthenticated,

View File

@ -9,14 +9,11 @@
* *
* @typedef {{ * @typedef {{
* display_name: string, * display_name: string,
* uri: string,
* id: string * id: string
* }} PlaylistOwner * }} User
* *
* @typedef {{ * @typedef {{
* name: string, * name: string,
* description: string,
* owner: PlaylistOwner,
* id: string, * id: string,
* }} SimplifiedPlaylist * }} SimplifiedPlaylist
* *
@ -45,7 +42,7 @@
* uri: string, * uri: string,
* name: string, * name: string,
* description: string, * description: string,
* owner: PlaylistOwner, * owner: User,
* followers: { * followers: {
* total: number * total: number
* }, * },

View File

@ -23,10 +23,11 @@ const axiosInstance = axios.default.create({
axiosInstance.interceptors.request.use(request => { axiosInstance.interceptors.request.use(request => {
logger.info("API call", { logger.info("API call", {
url: request.url, url: request.url,
params: request.params, method: request.method,
params: request.params ?? {},
}); });
return request; return request;
}) });
axiosInstance.interceptors.response.use( axiosInstance.interceptors.response.use(
(response) => response, (response) => response,
@ -62,9 +63,9 @@ axiosInstance.interceptors.response.use(
} }
return Promise.reject(error); return Promise.reject(error);
} }
) );
module.exports = { module.exports = {
authInstance, authInstance,
axiosInstance, axiosInstance
}; };

View File

@ -6,37 +6,37 @@ const { combine, label, timestamp, printf, errors } = format;
const typedefs = require("../typedefs"); const typedefs = require("../typedefs");
const getLabel = (callingModule) => { const getLabel = (callingModule) => {
const parts = callingModule.filename.split(path.sep); const parts = callingModule.filename.split(path.sep);
return path.join(parts[parts.length - 2], parts.pop()); return path.join(parts[parts.length - 2], parts.pop());
}; };
const logMetaReplacer = (key, value) => { const logMetaReplacer = (key, value) => {
if (key === "error") { if (key === "error") {
return { return {
name: value.name, name: value.name,
message: value.message, message: value.message,
stack: value.stack 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, "\t") + '\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) {
const allowedErrorKeys = ["name", "message", "stack"] const allowedErrorKeys = ["name", "message", "stack"]
if (typeof key !== "symbol" && !allowedErrorKeys.includes(key)) { if (typeof key !== "symbol" && !allowedErrorKeys.includes(key)) {
delete meta.error[key] delete meta.error[key]
} }
}
} }
} return `${timestamp} [${label}] ${level}: ${message}${metaFormat(meta)}`;
return `${timestamp} [${label}] ${level}: ${message}${metaFormat(meta)}`;
}); });
/** /**
@ -45,22 +45,22 @@ const logFormat = printf(({ level, message, label, timestamp, ...meta }) => {
* @returns {typedefs.Logger} * @returns {typedefs.Logger}
*/ */
const logger = (callingModule) => { const logger = (callingModule) => {
return createLogger({ return createLogger({
levels: config.npm.levels, levels: config.npm.levels,
format: combine( format: combine(
errors({ stack: true }), 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,
), ),
transports: [ transports: [
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' }),
] ]
}); });
} }
module.exports = logger; module.exports = logger;

View File

@ -24,9 +24,9 @@ const validate = (req, res, next) => {
return res.status(400).json({ return res.status(400).json({
message: getNestedValuesString(extractedErrors), message: getNestedValuesString(extractedErrors),
errors: extractedErrors errors: extractedErrors
}) });
} }
module.exports = { module.exports = {
validate, validate
} }

View File

@ -18,6 +18,5 @@ const getPlaylistDetailsValidator = async (req, res, next) => {
} }
module.exports = { module.exports = {
getPlaylistDetailsValidator, getPlaylistDetailsValidator
} }