mirror of
https://github.com/BreizhHardware/Jellystat.git
synced 2026-01-18 16:27:20 +01:00
Code clean-up + API Keys
Cleaned up redundant code and moved around classes to be better grouped eg routes folder Moved endpoints to other route files to better represent their actions Removed redundant endpoints Renamed endpoints to be more meaningful Added API Key authorisations to utilize the API outside of Jellystat UI (Still need to document Endpoints) Added new column to app_config to store api keys New API Section under settings Updated backups route name
This commit is contained in:
@@ -14,7 +14,7 @@ exports.up = async function(knex) {
|
||||
|
||||
exports.down = async function(knex) {
|
||||
try {
|
||||
await knex.schema.alterTable('jf_activity_watchdog', function(table) {
|
||||
await knex.schema.alterTable('app_config', function(table) {
|
||||
table.dropColumn('settings');
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
23
backend/migrations/042_app_config_add_api_keys_column.js
Normal file
23
backend/migrations/042_app_config_add_api_keys_column.js
Normal file
@@ -0,0 +1,23 @@
|
||||
exports.up = async function(knex) {
|
||||
try
|
||||
{
|
||||
const hasTable = await knex.schema.hasTable('app_config');
|
||||
if (hasTable) {
|
||||
await knex.schema.alterTable('app_config', function(table) {
|
||||
table.json('api_keys');
|
||||
});
|
||||
}
|
||||
}catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
exports.down = async function(knex) {
|
||||
try {
|
||||
await knex.schema.alterTable('app_config', function(table) {
|
||||
table.dropColumn('api_keys');
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
const express = require("express");
|
||||
const db = require("./db");
|
||||
const db = require("../db");
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
|
||||
@@ -240,7 +240,7 @@ async function restore(file,logData,result) {
|
||||
}
|
||||
|
||||
// Route handler for backup endpoint
|
||||
router.get('/backup', async (req, res) => {
|
||||
router.get('/beginBackup', async (req, res) => {
|
||||
try {
|
||||
let startTime = moment();
|
||||
let refLog={logData:[],result:'Success'};
|
||||
@@ -1,9 +1,9 @@
|
||||
|
||||
const db = require("./db");
|
||||
const db = require("../db");
|
||||
|
||||
|
||||
|
||||
const {jf_logging_columns,jf_logging_mapping,} = require("./models/jf_logging");
|
||||
const {jf_logging_columns,jf_logging_mapping,} = require("../models/jf_logging");
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const express = require('express');
|
||||
const axios = require("axios");
|
||||
const db = require("./db");
|
||||
const db = require("../db");
|
||||
const https = require('https');
|
||||
|
||||
const agent = new https.Agent({
|
||||
@@ -143,6 +143,141 @@ router.get('/Items/Images/Backdrop/', async(req, res) => {
|
||||
res.status(500).send('Error fetching image: '+error);
|
||||
});
|
||||
});
|
||||
|
||||
router.get("/getSessions", async (req, res) => {
|
||||
try {
|
||||
const { rows: config } = await db.query(
|
||||
'SELECT * FROM app_config where "ID"=1'
|
||||
);
|
||||
|
||||
if (
|
||||
config.length === 0 ||
|
||||
config[0].JF_HOST === null ||
|
||||
config[0].JF_API_KEY === null
|
||||
) {
|
||||
res.status(503);
|
||||
res.send({ error: "Config Details Not Found" });
|
||||
return;
|
||||
}
|
||||
|
||||
let url = `${config[0].JF_HOST}/sessions`;
|
||||
|
||||
const response_data = await axios_instance.get(url, {
|
||||
headers: {
|
||||
"X-MediaBrowser-Token": config[0].JF_API_KEY,
|
||||
},
|
||||
});
|
||||
res.send(response_data.data);
|
||||
} catch (error) {
|
||||
res.status(503);
|
||||
res.send(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/getAdminUsers", async (req, res) => {
|
||||
try {
|
||||
const { rows: config } = await db.query(
|
||||
'SELECT * FROM app_config where "ID"=1'
|
||||
);
|
||||
|
||||
if (
|
||||
config.length === 0 ||
|
||||
config[0].JF_HOST === null ||
|
||||
config[0].JF_API_KEY === null
|
||||
) {
|
||||
res.status(503);
|
||||
res.send({ error: "Config Details Not Found" });
|
||||
return;
|
||||
}
|
||||
|
||||
let url = `${config[0].JF_HOST}/Users`;
|
||||
|
||||
const response = await axios_instance.get(url, {
|
||||
headers: {
|
||||
"X-MediaBrowser-Token": config[0].JF_API_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
if(!response || typeof response.data !== 'object' || !Array.isArray(response.data))
|
||||
{
|
||||
res.status(503);
|
||||
res.send({ error: "Invalid Response from Users API Call.", user_response:response });
|
||||
return;
|
||||
}
|
||||
|
||||
const adminUser = response.data.filter(
|
||||
(user) => user.Policy.IsAdministrator === true
|
||||
);
|
||||
|
||||
res.send(adminUser);
|
||||
} catch (error) {
|
||||
res.status(503);
|
||||
res.send(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/getRecentlyAdded", async (req, res) => {
|
||||
try {
|
||||
|
||||
const { libraryid } = req.query;
|
||||
const { rows: config } = await db.query('SELECT * FROM app_config where "ID"=1');
|
||||
|
||||
if (config.length===0 || config[0].JF_HOST === null || config[0].JF_API_KEY === null) {
|
||||
res.status(503);
|
||||
res.send({ error: "Config Details Not Found" });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
let userid=config[0].settings?.preferred_admin?.userid;
|
||||
|
||||
if(!userid)
|
||||
{
|
||||
const adminurl = `${config[0].JF_HOST}/Users`;
|
||||
|
||||
const response = await axios_instance.get(adminurl, {
|
||||
headers: {
|
||||
"X-MediaBrowser-Token": config[0].JF_API_KEY ,
|
||||
},
|
||||
});
|
||||
|
||||
if(!response || typeof response.data !== 'object' || !Array.isArray(response.data))
|
||||
{
|
||||
res.status(503);
|
||||
res.send({ error: "Invalid Response from Users API Call.", user_response:response });
|
||||
return;
|
||||
}
|
||||
|
||||
const admins = response.data.filter(
|
||||
(user) => user.Policy.IsAdministrator === true
|
||||
);
|
||||
userid = admins[0].Id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
let url=`${config[0].JF_HOST}/users/${userid}/Items/latest`;
|
||||
if(libraryid)
|
||||
{
|
||||
url+=`?parentId=${libraryid}`;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const response_data = await axios_instance.get(url, {
|
||||
headers: {
|
||||
"X-MediaBrowser-Token": config[0].JF_API_KEY ,
|
||||
},
|
||||
});
|
||||
res.send(response_data.data);
|
||||
} catch (error) {
|
||||
res.status(503);
|
||||
res.send(error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
// api.js
|
||||
const express = require("express");
|
||||
const db = require("./db");
|
||||
const axios=require("axios");
|
||||
const db = require("../db");
|
||||
|
||||
const router = express.Router();
|
||||
const https = require('https');
|
||||
|
||||
|
||||
|
||||
const agent = new https.Agent({
|
||||
rejectUnauthorized: (process.env.REJECT_SELF_SIGNED_CERTIFICATES || 'true').toLowerCase() ==='true'
|
||||
});
|
||||
const axios_instance = axios.create({
|
||||
httpsAgent: agent
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -27,15 +16,26 @@ router.get("/getLibraryOverview", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/getMostViewedSeries", async (req, res) => {
|
||||
router.post("/getMostViewedByType", async (req, res) => {
|
||||
try {
|
||||
const { days } = req.body;
|
||||
const { days,type } = req.body;
|
||||
|
||||
const valid_types=['Audio','Movie','Series'];
|
||||
|
||||
let _days = days;
|
||||
if (days === undefined) {
|
||||
_days = 30;
|
||||
}
|
||||
|
||||
if(!valid_types.includes(type))
|
||||
{
|
||||
res.status(503);
|
||||
return res.send('Invalid Type Value');
|
||||
}
|
||||
|
||||
|
||||
const { rows } = await db.query(
|
||||
`select * from fs_most_played_items(${_days-1},'Series') limit 5`
|
||||
`select * from fs_most_played_items(${_days-1},'${type}') limit 5`
|
||||
);
|
||||
res.send(rows);
|
||||
} catch (error) {
|
||||
@@ -44,41 +44,34 @@ router.post("/getMostViewedSeries", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/getMostViewedMovies", async (req, res) => {
|
||||
router.post("/getMostPopularByType", async (req, res) => {
|
||||
try {
|
||||
const { days } = req.body;
|
||||
const { days,type } = req.body;
|
||||
|
||||
const valid_types=['Audio','Movie','Series'];
|
||||
|
||||
let _days = days;
|
||||
if (days === undefined) {
|
||||
_days = 30;
|
||||
}
|
||||
|
||||
if(!valid_types.includes(type))
|
||||
{
|
||||
res.status(503);
|
||||
return res.send('Invalid Type Value');
|
||||
}
|
||||
|
||||
const { rows } = await db.query(
|
||||
`select * from fs_most_played_items(${_days-1},'Movie') limit 5`
|
||||
`select * from fs_most_popular_items(${_days-1},'${type}') limit 5`
|
||||
);
|
||||
res.send(rows);
|
||||
} catch (error) {
|
||||
console.log('/getMostViewedMovies');
|
||||
console.log(error);
|
||||
res.status(503);
|
||||
res.send(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/getMostViewedMusic", async (req, res) => {
|
||||
try {
|
||||
const { days } = req.body;
|
||||
let _days = days;
|
||||
if (days === undefined) {
|
||||
_days = 30;
|
||||
}
|
||||
const { rows } = await db.query(
|
||||
`select * from fs_most_played_items(${_days-1},'Audio') limit 5`
|
||||
);
|
||||
res.send(rows);
|
||||
} catch (error) {
|
||||
res.status(503);
|
||||
res.send(error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
router.post("/getMostViewedLibraries", async (req, res) => {
|
||||
try {
|
||||
@@ -131,56 +124,6 @@ router.post("/getMostActiveUsers", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/getMostPopularMovies", async (req, res) => {
|
||||
try {
|
||||
const { days } = req.body;
|
||||
let _days = days;
|
||||
if (days === undefined) {
|
||||
_days = 30;
|
||||
}
|
||||
const { rows } = await db.query(
|
||||
`select * from fs_most_popular_items(${_days-1},'Movie') limit 5`
|
||||
);
|
||||
res.send(rows);
|
||||
} catch (error) {
|
||||
res.status(503);
|
||||
res.send(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/getMostPopularSeries", async (req, res) => {
|
||||
try {
|
||||
const { days } = req.body;
|
||||
let _days = days;
|
||||
if (days === undefined) {
|
||||
_days = 30;
|
||||
}
|
||||
const { rows } = await db.query(
|
||||
`select * from fs_most_popular_items(${_days-1},'Series') limit 5`
|
||||
);
|
||||
res.send(rows);
|
||||
} catch (error) {
|
||||
res.status(503);
|
||||
res.send(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/getMostPopularMusic", async (req, res) => {
|
||||
try {
|
||||
const { days } = req.body;
|
||||
let _days = days;
|
||||
if (days === undefined) {
|
||||
_days = 30;
|
||||
}
|
||||
const { rows } = await db.query(
|
||||
`select * from fs_most_popular_items(${_days-1},'Audio') limit 5`
|
||||
);
|
||||
res.send(rows);
|
||||
} catch (error) {
|
||||
res.status(503);
|
||||
res.send(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/getPlaybackActivity", async (req, res) => {
|
||||
try {
|
||||
@@ -201,13 +144,14 @@ router.get("/getAllUserActivity", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/getUserDetails", async (req, res) => {
|
||||
|
||||
router.post("/getUserLastPlayed", async (req, res) => {
|
||||
try {
|
||||
const { userid } = req.body;
|
||||
const { rows } = await db.query(
|
||||
`select * from jf_users where "Id"='${userid}'`
|
||||
`select * from fs_last_user_activity('${userid}') limit 15`
|
||||
);
|
||||
res.send(rows[0]);
|
||||
res.send(rows);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(503);
|
||||
@@ -215,6 +159,7 @@ router.post("/getUserDetails", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
//Global Stats
|
||||
router.post("/getGlobalUserStats", async (req, res) => {
|
||||
try {
|
||||
const { hours,userid } = req.body;
|
||||
@@ -233,25 +178,20 @@ router.post("/getGlobalUserStats", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/getUserLastPlayed", async (req, res) => {
|
||||
router.post("/getGlobalItemStats", async (req, res) => {
|
||||
try {
|
||||
const { userid } = req.body;
|
||||
const { hours,itemid } = req.body;
|
||||
let _hours = hours;
|
||||
if (hours === undefined) {
|
||||
_hours = 24;
|
||||
}
|
||||
const { rows } = await db.query(
|
||||
`select * from fs_last_user_activity('${userid}') limit 15`
|
||||
);
|
||||
res.send(rows);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(503);
|
||||
res.send(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/getLibraryDetails", async (req, res) => {
|
||||
try {
|
||||
const { libraryid } = req.body;
|
||||
const { rows } = await db.query(
|
||||
`select * from jf_libraries where "Id"='${libraryid}'`
|
||||
`select count(*)"Plays",
|
||||
sum("PlaybackDuration") total_playback_duration
|
||||
from jf_playback_activity jf_playback_activity
|
||||
where
|
||||
("EpisodeId"='${itemid}' OR "SeasonId"='${itemid}' OR "NowPlayingItemId"='${itemid}')
|
||||
AND jf_playback_activity."ActivityDateInserted" BETWEEN CURRENT_DATE - INTERVAL '1 hour' * ${_hours} AND NOW();`
|
||||
);
|
||||
res.send(rows[0]);
|
||||
} catch (error) {
|
||||
@@ -280,6 +220,8 @@ router.post("/getGlobalLibraryStats", async (req, res) => {
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
router.get("/getLibraryCardStats", async (req, res) => {
|
||||
try {
|
||||
const { rows } = await db.query("select * from js_library_stats_overview");
|
||||
@@ -333,60 +275,6 @@ router.post("/getLibraryLastPlayed", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/getRecentlyAdded", async (req, res) => {
|
||||
try {
|
||||
|
||||
const { libraryid } = req.query;
|
||||
const { rows: config } = await db.query('SELECT * FROM app_config where "ID"=1');
|
||||
|
||||
if (config.length===0 || config[0].JF_HOST === null || config[0].JF_API_KEY === null) {
|
||||
res.status(503);
|
||||
res.send({ error: "Config Details Not Found" });
|
||||
return;
|
||||
}
|
||||
|
||||
const adminurl = `${config[0].JF_HOST}/Users`;
|
||||
|
||||
const response = await axios_instance.get(adminurl, {
|
||||
headers: {
|
||||
"X-MediaBrowser-Token": config[0].JF_API_KEY ,
|
||||
},
|
||||
});
|
||||
|
||||
if(!response || typeof response.data !== 'object' || !Array.isArray(response.data))
|
||||
{
|
||||
res.status(503);
|
||||
res.send({ error: "Invalid Response from Users API Call.", user_response:response });
|
||||
return;
|
||||
}
|
||||
|
||||
const adminUser = response.data.filter(
|
||||
(user) => user.Policy.IsAdministrator === true
|
||||
);
|
||||
|
||||
|
||||
let url=`${config[0].JF_HOST}/users/${adminUser[0].Id}/Items/latest`;
|
||||
if(libraryid)
|
||||
{
|
||||
url+=`?parentId=${libraryid}`;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const response_data = await axios_instance.get(url, {
|
||||
headers: {
|
||||
"X-MediaBrowser-Token": config[0].JF_API_KEY ,
|
||||
},
|
||||
});
|
||||
res.send(response_data.data);
|
||||
} catch (error) {
|
||||
res.status(503);
|
||||
res.send(error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
router.post("/getViewsOverTime", async (req, res) => {
|
||||
try {
|
||||
@@ -474,7 +362,6 @@ const finalData = {libraries:libraries,stats:Object.values(reorganizedData)};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
router.post("/getViewsByHour", async (req, res) => {
|
||||
try {
|
||||
const { days } = req.body;
|
||||
@@ -516,29 +403,6 @@ const finalData = {libraries:libraries,stats:Object.values(reorganizedData)};
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/getGlobalItemStats", async (req, res) => {
|
||||
try {
|
||||
const { hours,itemid } = req.body;
|
||||
let _hours = hours;
|
||||
if (hours === undefined) {
|
||||
_hours = 24;
|
||||
}
|
||||
const { rows } = await db.query(
|
||||
`select count(*)"Plays",
|
||||
sum("PlaybackDuration") total_playback_duration
|
||||
from jf_playback_activity jf_playback_activity
|
||||
where
|
||||
("EpisodeId"='${itemid}' OR "SeasonId"='${itemid}' OR "NowPlayingItemId"='${itemid}')
|
||||
AND jf_playback_activity."ActivityDateInserted" BETWEEN CURRENT_DATE - INTERVAL '1 hour' * ${_hours} AND NOW();`
|
||||
);
|
||||
res.send(rows[0]);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(503);
|
||||
res.send(error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const express = require("express");
|
||||
const pgp = require("pg-promise")();
|
||||
const db = require("./db");
|
||||
const db = require("../db");
|
||||
const axios = require("axios");
|
||||
const https = require('https');
|
||||
|
||||
@@ -25,14 +25,14 @@ const { randomUUID } = require('crypto');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const {jf_libraries_columns,jf_libraries_mapping,} = require("./models/jf_libraries");
|
||||
const {jf_library_items_columns,jf_library_items_mapping,} = require("./models/jf_library_items");
|
||||
const {jf_library_seasons_columns,jf_library_seasons_mapping,} = require("./models/jf_library_seasons");
|
||||
const {jf_library_episodes_columns,jf_library_episodes_mapping,} = require("./models/jf_library_episodes");
|
||||
const {jf_item_info_columns,jf_item_info_mapping,} = require("./models/jf_item_info");
|
||||
const {columnsPlaybackReporting,mappingPlaybackReporting}= require("./models/jf_playback_reporting_plugin_data");
|
||||
const {jf_libraries_columns,jf_libraries_mapping,} = require("../models/jf_libraries");
|
||||
const {jf_library_items_columns,jf_library_items_mapping,} = require("../models/jf_library_items");
|
||||
const {jf_library_seasons_columns,jf_library_seasons_mapping,} = require("../models/jf_library_seasons");
|
||||
const {jf_library_episodes_columns,jf_library_episodes_mapping,} = require("../models/jf_library_episodes");
|
||||
const {jf_item_info_columns,jf_item_info_mapping,} = require("../models/jf_item_info");
|
||||
const {columnsPlaybackReporting,mappingPlaybackReporting}= require("../models/jf_playback_reporting_plugin_data");
|
||||
|
||||
const {jf_users_columns,jf_users_mapping,} = require("./models/jf_users");
|
||||
const {jf_users_columns,jf_users_mapping,} = require("../models/jf_users");
|
||||
|
||||
/////////////////////////////////////////Functions
|
||||
|
||||
@@ -5,16 +5,19 @@ const knex = require('knex');
|
||||
const createdb = require('./create_database');
|
||||
const knexConfig = require('./migrations');
|
||||
|
||||
const authRouter= require('./auth');
|
||||
const apiRouter = require('./api');
|
||||
const proxyRouter = require('./proxy');
|
||||
const {router: syncRouter} = require('./sync');
|
||||
const statsRouter = require('./stats');
|
||||
const {router: backupRouter} = require('./backup');
|
||||
const authRouter= require('./routes/auth');
|
||||
const apiRouter = require('./routes/api');
|
||||
const proxyRouter = require('./routes/proxy');
|
||||
const {router: syncRouter} = require('./routes/sync');
|
||||
const statsRouter = require('./routes/stats');
|
||||
const {router: backupRouter} = require('./routes/backup');
|
||||
const ActivityMonitor = require('./tasks/ActivityMonitor');
|
||||
const SyncTask = require('./tasks/SyncTask');
|
||||
const BackupTask = require('./tasks/BackupTask');
|
||||
const {router: logRouter} = require('./logging');
|
||||
const {router: logRouter} = require('./routes/logging');
|
||||
|
||||
const dbInstance = require("./db");
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -36,29 +39,70 @@ app.use(cors());
|
||||
|
||||
|
||||
// JWT middleware
|
||||
function verifyToken(req, res, next) {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (authHeader) {
|
||||
const token = authHeader.split(' ')[1];
|
||||
jwt.verify(token, JWT_SECRET, (err, user) => {
|
||||
if (err) {
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
req.user = user;
|
||||
async function authenticate (req, res, next) {
|
||||
const token = req.headers.authorization;
|
||||
const apiKey = req.headers['x-api-token'] || req.query.apiKey;
|
||||
|
||||
|
||||
if (!token && !apiKey) {
|
||||
return res.status(401).json({ message: 'Authentication failed. No token or API key provided.' });
|
||||
}
|
||||
|
||||
if (token) {
|
||||
const extracted_token=token.split(' ')[1];
|
||||
if(!extracted_token || extracted_token==='null')
|
||||
{
|
||||
return res.sendStatus(403);
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(extracted_token, JWT_SECRET);
|
||||
req.user = decoded.user;
|
||||
next();
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return res.status(401).json({ message: 'Invalid token' });
|
||||
}
|
||||
} else {
|
||||
res.sendStatus(401);
|
||||
|
||||
|
||||
|
||||
if (apiKey) {
|
||||
const keysjson = await dbInstance
|
||||
.query('SELECT api_keys FROM app_config where "ID"=1')
|
||||
.then((res) => res.rows[0].api_keys);
|
||||
|
||||
|
||||
if(!keysjson || Object.keys(keysjson).length===0)
|
||||
{
|
||||
return res.status(404).json({ message: 'No API keys configured' });
|
||||
}
|
||||
const keys= keysjson || [];
|
||||
|
||||
const keyExists = keys.some(obj => obj.key === apiKey);
|
||||
|
||||
if(keyExists)
|
||||
{
|
||||
next();
|
||||
}else
|
||||
{
|
||||
return res.status(403).json({ message: 'Invalid API key' });
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
app.use('/auth', authRouter); // mount the API router at /api, with JWT middleware
|
||||
app.use('/api', verifyToken, apiRouter); // mount the API router at /api, with JWT middleware
|
||||
app.use('/proxy', proxyRouter); // mount the API router at /api, with JWT middleware
|
||||
app.use('/sync', verifyToken, syncRouter); // mount the API router at /sync, with JWT middleware
|
||||
app.use('/stats', verifyToken, statsRouter); // mount the API router at /stats, with JWT middleware
|
||||
app.use('/data', verifyToken, backupRouter); // mount the API router at /stats, with JWT middleware
|
||||
app.use('/logs', verifyToken, logRouter); // mount the API router at /stats, with JWT middleware
|
||||
app.use('/api', authenticate , apiRouter); // mount the API router at /api, with JWT middleware
|
||||
app.use('/sync', authenticate , syncRouter); // mount the API router at /sync, with JWT middleware
|
||||
app.use('/stats', authenticate , statsRouter); // mount the API router at /stats, with JWT middleware
|
||||
app.use('/backup', authenticate , backupRouter); // mount the API router at /stats, with JWT middleware
|
||||
app.use('/logs', authenticate , logRouter); // mount the API router at /stats, with JWT middleware
|
||||
|
||||
try{
|
||||
createdb.createDatabase().then((result) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const db = require("../db");
|
||||
const Logging = require("../logging");
|
||||
const Logging = require("../routes/logging");
|
||||
|
||||
const backup = require("../backup");
|
||||
const backup = require("../routes/backup");
|
||||
const moment = require('moment');
|
||||
const { randomUUID } = require('crypto');
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const db = require("../db");
|
||||
const moment = require('moment');
|
||||
const sync = require("../sync");
|
||||
const sync = require("../routes/sync");
|
||||
|
||||
async function SyncTask() {
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ function LibraryInfo() {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
console.log('getdata');
|
||||
const libraryrData = await axios.post(`/stats/getLibraryDetails`, {
|
||||
const libraryrData = await axios.post(`/api/getLibrary`, {
|
||||
libraryid: LibraryId,
|
||||
}, {
|
||||
headers: {
|
||||
|
||||
@@ -18,7 +18,7 @@ function RecentlyAdded(props) {
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
let url=`/stats/getRecentlyAdded`;
|
||||
let url=`/proxy/getRecentlyAdded`;
|
||||
if(props.LibraryId)
|
||||
{
|
||||
url+=`?libraryid=${props.LibraryId}`;
|
||||
|
||||
@@ -27,7 +27,7 @@ function Sessions() {
|
||||
const fetchData = () => {
|
||||
|
||||
if (config) {
|
||||
const url = `/api/getSessions`;
|
||||
const url = `/proxy/getSessions`;
|
||||
|
||||
axios
|
||||
.get(url, {
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function Tasks() {
|
||||
setProcessing(true);
|
||||
|
||||
await axios
|
||||
.get("/data/backup", {
|
||||
.get("/backup/beginBackup", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
|
||||
229
src/pages/components/settings/apiKeys.js
Normal file
229
src/pages/components/settings/apiKeys.js
Normal file
@@ -0,0 +1,229 @@
|
||||
import React, { useState,useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import {Form, Row, Col,ButtonGroup, Button } from 'react-bootstrap';
|
||||
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableContainer from '@mui/material/TableContainer';
|
||||
import TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
|
||||
|
||||
|
||||
import Alert from "react-bootstrap/Alert";
|
||||
|
||||
|
||||
|
||||
import "../../css/settings/backups.css";
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
|
||||
function CustomRow(key) {
|
||||
const { data } = key;
|
||||
|
||||
|
||||
|
||||
async function deleteKey(keyvalue) {
|
||||
const url=`/api/keys`;
|
||||
axios
|
||||
.delete(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
data: {
|
||||
key: keyvalue,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
const alert={visible:true,title:'Success',type:'success',message:response.data};
|
||||
key.handleRowActionMessage(alert);
|
||||
})
|
||||
.catch((error) => {
|
||||
const alert={visible:true,title:'Error',type:'danger',message:error.response.data};
|
||||
key.handleRowActionMessage(alert);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
|
||||
<TableCell>{data.name}</TableCell>
|
||||
<TableCell>{data.key}</TableCell>
|
||||
|
||||
<TableCell className="">
|
||||
<div className="d-flex justify-content-center">
|
||||
<Button variant="primary" onClick={()=>deleteKey(data.key)}>Delete</Button>
|
||||
</div>
|
||||
|
||||
</TableCell>
|
||||
|
||||
</TableRow>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default function ApiKeys() {
|
||||
const [keys, setKeys] = useState([]);
|
||||
const [showAlert, setshowAlert] = useState({visible:false,type:'danger',title:'Error',message:''});
|
||||
const [rowsPerPage] = React.useState(10);
|
||||
const [page, setPage] = React.useState(0);
|
||||
const [formValues, setFormValues] = useState({});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function handleCloseAlert() {
|
||||
setshowAlert({visible:false});
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const apiKeyData = await axios.get(`/api/keys`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
setKeys(apiKeyData.data);
|
||||
console.log(apiKeyData);
|
||||
console.log('KeyData');
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
|
||||
const intervalId = setInterval(fetchData, 1000 * 5);
|
||||
return () => clearInterval(intervalId);
|
||||
}, [token]);
|
||||
|
||||
|
||||
const handleNextPageClick = () => {
|
||||
setPage((prevPage) => prevPage + 1);
|
||||
};
|
||||
|
||||
const handlePreviousPageClick = () => {
|
||||
setPage((prevPage) => prevPage - 1);
|
||||
};
|
||||
|
||||
const handleRowActionMessage= (alertState) => {
|
||||
console.log(alertState);
|
||||
setshowAlert({visible:alertState.visible,title:alertState.title,type:alertState.type,message:alertState.message});
|
||||
};
|
||||
|
||||
function handleFormChange(event) {
|
||||
setFormValues({ ...formValues, [event.target.name]: event.target.value });
|
||||
}
|
||||
|
||||
async function handleFormSubmit(event) {
|
||||
event.preventDefault();
|
||||
axios
|
||||
.post("/api/keys/", formValues, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
console.log("API key added successfully:", response.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Error adding key:", error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="my-2">API Keys</h1>
|
||||
{showAlert && showAlert.visible && (
|
||||
<Alert variant={showAlert.type} onClose={handleCloseAlert} dismissible>
|
||||
<Alert.Heading>{showAlert.title}</Alert.Heading>
|
||||
<p>
|
||||
{showAlert.message}
|
||||
</p>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
|
||||
<Form onSubmit={handleFormSubmit} className="settings-form">
|
||||
|
||||
<Form.Group as={Row} className="mb-3" >
|
||||
<Form.Label column>
|
||||
KEY NAME
|
||||
</Form.Label>
|
||||
<Col sm="6" md="8">
|
||||
<Form.Control className="w-100" id="name" name="name" value={formValues.name || ""} onChange={handleFormChange} placeholder="API Name" />
|
||||
</Col>
|
||||
<Col sm="4" md="2" className="mt-2 mt-sm-0">
|
||||
<Button variant="outline-primary" type="submit" className="w-100"> Add Key </Button>
|
||||
</Col>
|
||||
|
||||
</Form.Group>
|
||||
|
||||
|
||||
</Form>
|
||||
|
||||
<TableContainer className='rounded-2'>
|
||||
<Table aria-label="collapsible table" >
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Key</TableCell>
|
||||
<TableCell></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{keys && keys.sort((a, b) => a.name-b.name).slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
|
||||
.map((apikey,index) => (
|
||||
<CustomRow key={index} data={apikey} handleRowActionMessage={handleRowActionMessage}/>
|
||||
))}
|
||||
{keys.length===0 ? <tr><td colSpan="3" style={{ textAlign: "center", fontStyle: "italic" ,color:"grey"}} className='py-2'>No Keys Found</td></tr> :''}
|
||||
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
<div className='d-flex justify-content-end my-2'>
|
||||
|
||||
|
||||
|
||||
<ButtonGroup className="pagination-buttons">
|
||||
<Button className="page-btn" onClick={()=>setPage(0)} disabled={page === 0}>
|
||||
First
|
||||
</Button>
|
||||
|
||||
<Button className="page-btn" onClick={handlePreviousPageClick} disabled={page === 0}>
|
||||
Previous
|
||||
</Button>
|
||||
|
||||
<div className="page-number d-flex align-items-center justify-content-center">{`${page *rowsPerPage + 1}-${Math.min((page * rowsPerPage+ 1 ) + (rowsPerPage - 1),keys.length)} of ${keys.length}`}</div>
|
||||
|
||||
<Button className="page-btn" onClick={handleNextPageClick} disabled={page >= Math.ceil(keys.length / rowsPerPage) - 1}>
|
||||
Next
|
||||
</Button>
|
||||
|
||||
<Button className="page-btn" onClick={()=>setPage(Math.ceil(keys.length / rowsPerPage) - 1)} disabled={page >= Math.ceil(keys.length / rowsPerPage) - 1}>
|
||||
Last
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
@@ -25,7 +25,7 @@ function Row(file) {
|
||||
const [disabled, setDisabled] = useState(false);
|
||||
|
||||
async function downloadBackup(filename) {
|
||||
const url=`/data/files/${filename}`;
|
||||
const url=`/backup/files/${filename}`;
|
||||
axios({
|
||||
url: url,
|
||||
headers: {
|
||||
@@ -46,7 +46,7 @@ function Row(file) {
|
||||
}
|
||||
|
||||
async function restoreBackup(filename) {
|
||||
const url=`/data/restore/${filename}`;
|
||||
const url=`/backup/restore/${filename}`;
|
||||
setDisabled(true);
|
||||
axios
|
||||
.get(url, {
|
||||
@@ -70,7 +70,7 @@ function Row(file) {
|
||||
}
|
||||
|
||||
async function deleteBackup(filename) {
|
||||
const url=`/data/files/${filename}`;
|
||||
const url=`/backup/files/${filename}`;
|
||||
setDisabled(true);
|
||||
axios
|
||||
.delete(url, {
|
||||
@@ -174,7 +174,7 @@ const uploadFile = (file, onUploadProgress) => {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
return axios.post("/data/upload", formData, {
|
||||
return axios.post("/backup/upload", formData, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "multipart/form-data",
|
||||
@@ -199,7 +199,7 @@ const handleFileSelect = (event) => {
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const backupFiles = await axios.get(`/data/files`, {
|
||||
const backupFiles = await axios.get(`/backup/files`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function SettingsConfig() {
|
||||
const fetchAdmins = async () => {
|
||||
try {
|
||||
|
||||
const adminData = await axios.get(`/api/getAdminUsers`, {
|
||||
const adminData = await axios.get(`/proxy/getAdminUsers`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -28,10 +28,10 @@ function MPMovies(props) {
|
||||
|
||||
const fetchLibraries = () => {
|
||||
if (config) {
|
||||
const url = `/stats/getMostPopularMovies`;
|
||||
const url = `/stats/getMostPopularByType`;
|
||||
|
||||
axios
|
||||
.post(url, {days:props.days}, {
|
||||
.post(url, {days:props.days, type:'Movie'}, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.token}`,
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -26,10 +26,10 @@ function MPMusic(props) {
|
||||
|
||||
const fetchLibraries = () => {
|
||||
if (config) {
|
||||
const url = `/stats/getMostPopularMusic`;
|
||||
const url = `/stats/getMostPopularByType`;
|
||||
|
||||
axios
|
||||
.post(url, { days: props.days }, {
|
||||
.post(url, { days: props.days, type:'Audio' }, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.token}`,
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -24,10 +24,10 @@ function MPSeries(props) {
|
||||
|
||||
const fetchLibraries = () => {
|
||||
if (config) {
|
||||
const url = `/stats/getMostPopularSeries`;
|
||||
const url = `/stats/getMostPopularByType`;
|
||||
|
||||
axios
|
||||
.post(url, { days: props.days }, {
|
||||
.post(url, { days: props.days, type:'Series' }, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.token}`,
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -29,10 +29,10 @@ function MVMusic(props) {
|
||||
|
||||
const fetchLibraries = () => {
|
||||
if (config) {
|
||||
const url = `/stats/getMostViewedMovies`;
|
||||
const url = `/stats/getMostViewedByType`;
|
||||
|
||||
axios
|
||||
.post(url, {days:props.days}, {
|
||||
.post(url, {days:props.days, type:'Movie'}, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.token}`,
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -24,10 +24,10 @@ function MVMovies(props) {
|
||||
|
||||
const fetchLibraries = () => {
|
||||
if (config) {
|
||||
const url = `/stats/getMostViewedMusic`;
|
||||
const url = `/stats/getMostViewedByType`;
|
||||
|
||||
axios
|
||||
.post(url, {days:props.days}, {
|
||||
.post(url, {days:props.days, type:'Audio'}, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.token}`,
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -27,10 +27,10 @@ function MVSeries(props) {
|
||||
|
||||
const fetchLibraries = () => {
|
||||
if (config) {
|
||||
const url = `/stats/getMostViewedSeries`;
|
||||
const url = `/stats/getMostViewedByType`;
|
||||
|
||||
axios
|
||||
.post(url, {days:props.days}, {
|
||||
.post(url, {days:props.days, type:'Series'}, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.token}`,
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -35,7 +35,7 @@ function UserInfo() {
|
||||
const fetchData = async () => {
|
||||
if(config){
|
||||
try {
|
||||
const userData = await axios.post(`/stats/getUserDetails`, {
|
||||
const userData = await axios.post(`/api/getUserDetails`, {
|
||||
userid: UserId,
|
||||
}, {
|
||||
headers: {
|
||||
|
||||
@@ -5,6 +5,7 @@ import SettingsConfig from "./components/settings/settingsConfig";
|
||||
import Tasks from "./components/settings/Tasks";
|
||||
import BackupFiles from "./components/settings/backupfiles";
|
||||
import SecuritySettings from "./components/settings/security";
|
||||
import ApiKeys from "./components/settings/apiKeys";
|
||||
|
||||
import Logs from "./components/settings/logs";
|
||||
|
||||
@@ -34,6 +35,10 @@ export default function Settings() {
|
||||
<BackupFiles/>
|
||||
</Tab>
|
||||
|
||||
<Tab eventKey="tabKeys" className='bg-transparent my-2' title='API Keys' style={{minHeight:'500px'}}>
|
||||
<ApiKeys/>
|
||||
</Tab>
|
||||
|
||||
<Tab eventKey="tabLogs" className='bg-transparent my-2' title='Logs' style={{minHeight:'500px'}}>
|
||||
<Logs/>
|
||||
</Tab>
|
||||
|
||||
@@ -37,7 +37,7 @@ module.exports = function(app) {
|
||||
})
|
||||
);
|
||||
app.use(
|
||||
`/data`,
|
||||
`/backup`,
|
||||
createProxyMiddleware({
|
||||
target: `http://127.0.0.1:${process.env.PORT || 3003}`,
|
||||
changeOrigin: true,
|
||||
|
||||
Reference in New Issue
Block a user