mirror of
https://github.com/20kaushik02/spotify-manager.git
synced 2025-12-06 13:44:06 +00:00
ocd formatting, changed user object, retrieve user ID
This commit is contained in:
parent
40cf0c2e2b
commit
f067320a7f
4
.env
Normal file
4
.env
Normal 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
2
.env.development
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
REDIRECT_URI = http://localhost:9001/api/auth/callback
|
||||||
|
TRUST_PROXY=1
|
||||||
2
.env.production
Normal file
2
.env.production
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
REDIRECT_URI = https://domain.for.this.app/api/auth/callback
|
||||||
|
TRUST_PROXY=1
|
||||||
8
.gitignore
vendored
8
.gitignore
vendored
@ -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
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
# spotify-manager
|
# spotify-manager
|
||||||
|
|
||||||
Personal Spotify playlist manager. Features inbound!
|
Personal Spotify playlist manager. Features inbound!
|
||||||
|
|||||||
@ -17,6 +17,5 @@ const __validator_func = async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
__validator_func,
|
__validator_func
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 = {
|
||||||
|
|||||||
@ -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
|
||||||
};
|
};
|
||||||
@ -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
|
||||||
};
|
};
|
||||||
7
index.js
7
index.js
@ -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
|
||||||
|
|||||||
@ -26,5 +26,5 @@ const isAuthenticated = (req, res, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
isAuthenticated,
|
isAuthenticated
|
||||||
}
|
}
|
||||||
@ -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;
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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
|
||||||
* },
|
* },
|
||||||
|
|||||||
@ -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
|
||||||
};
|
};
|
||||||
@ -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;
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
@ -18,6 +18,5 @@ const getPlaylistDetailsValidator = async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getPlaylistDetailsValidator,
|
getPlaylistDetailsValidator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user