Added Data Debugger

This is to assist in the ongoing sync issues by allowing you to export any missing data for inspection
This commit is contained in:
Thegan Govender
2023-06-04 13:29:22 +02:00
parent 37bbfef0ba
commit d8b4360b3d
7 changed files with 316 additions and 6 deletions

21
Dockerfile-arm Normal file
View File

@@ -0,0 +1,21 @@
# Stage 1: Build the application
FROM arm32v7/node:14 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm cache clean --force
RUN npm install
COPY ./ ./
# Stage 2: Create the production image
FROM arm32v7/node:14-slim
WORKDIR /app
COPY --from=builder /app .
EXPOSE 3000
CMD ["npm", "run", "start-app"]

View File

@@ -524,6 +524,144 @@ router.post("/updatePassword", async (req, res) => {
});
router.post("/getLibraries", async (req, res) => {
try {
const { itemid } = req.body;
const { rows:config } = await db.query('SELECT * FROM app_config where "ID"=1');
let payload=
{
existing_library_count:0,
existing_item_count:0,
existing_season_count:0,
existing_episode_count:0,
api_library_count:0,
api_item_count:0,
api_season_count:0,
api_episode_count:0,
missing_api_library_data:{},
missing_api_item_data:{},
missing_api_season_data:{},
missing_api_episode_data:{},
};
/////////////////////////Get Admin
const adminurl = `${config[0].JF_HOST}/Users`;
const response = await axios_instance.get(adminurl, {
headers: {
"X-MediaBrowser-Token": config[0].JF_API_KEY,
},
});
const adminUser = await response.data.filter(
(user) => user.Policy.IsAdministrator === true
);
////////////////////////
const db_libraries=await db.query('SELECT "Id" FROM jf_libraries').then((res) => res.rows.map((row) => row.Id))
const db_items=await db.query('SELECT "Id" FROM jf_library_items').then((res) => res.rows.map((row) => row.Id))
const db_seasons=await db.query('SELECT "Id" FROM jf_library_seasons').then((res) => res.rows.map((row) => row.Id))
const db_episodes=await db.query('SELECT "EpisodeId" FROM jf_library_episodes').then((res) => res.rows.map((row) => row.EpisodeId))
//get libraries
let url=`${config[0].JF_HOST}/Users/${adminUser[0].Id}/Items`;
if(itemid)
{
url+=`/${itemid}`;
}
const response_data = await axios_instance.get(url, {
headers: {
"X-MediaBrowser-Token": config[0].JF_API_KEY ,
},
});
let libraries=response_data.data.Items;
//get items
const item_data = [];
for (let i = 0; i < libraries.length; i++) {
const library = libraries[i];
let item_url=`${config[0].JF_HOST}/Users/${adminUser[0].Id}/Items?ParentID=${library.Id}`;
const response_data_item = await axios_instance.get(item_url, {
headers: {
"X-MediaBrowser-Token": config[0].JF_API_KEY ,
},
});
const libraryItemsWithParent = response_data_item.data.Items.map((items) => ({
...items,
...{ ParentId: library.Id },
}));
item_data.push(...libraryItemsWithParent);
}
payload.existing_library_count=db_libraries.length;
payload.api_library_count=libraries.length;
payload.existing_item_count=db_items.length;
payload.api_item_count=item_data.length;
//SHows
let allSeasons = [];
let allEpisodes =[];
const { rows: shows } = await db.query(`SELECT "Id" FROM public.jf_library_items where "Type"='Series'`);
//loop for each show
for (const show of shows) {
let season_url = `${config[0].JF_HOST}/shows/${show.Id}/Seasons`;
let episodes_url = `${config[0].JF_HOST}/shows/${show.Id}/Episodes`;
const response_data_seasons = await axios_instance.get(season_url, {
headers: {
"X-MediaBrowser-Token": config[0].JF_API_KEY ,
},
});
const response_data_episodes = await axios_instance.get(episodes_url, {
headers: {
"X-MediaBrowser-Token": config[0].JF_API_KEY ,
},
});
allSeasons.push(...response_data_seasons.data.Items);
allEpisodes.push(...response_data_episodes.data.Items);
}
payload.existing_season_count=db_seasons.length;
payload.api_season_count=allSeasons.length;
payload.existing_episode_count=db_episodes.length;
payload.api_episode_count=allEpisodes.length;
//missing data section
let missing_libraries=libraries.filter(library => !db_libraries.includes(library.Id));
let missing_items=item_data.filter(item => !db_items.includes(item.Id));
let missing_seasons=allSeasons.filter(season => !db_seasons.includes(season.Id));
let missing_episodes=allEpisodes.filter(episode => !db_episodes.includes(episode.Id));
payload.missing_api_library_data=missing_libraries;
payload.missing_api_item_data=missing_items;
payload.missing_api_season_data=missing_seasons;
payload.missing_api_episode_data=missing_episodes;
res.send(payload);
} catch (error) {
console.log(error);
res.status(503);
res.send(error);
}
});

View File

@@ -127,9 +127,9 @@ class sync {
try {
let url = `${this.hostUrl}/shows/${itemID}/${type}`;
if (itemID !== undefined) {
url += `?ParentID=${itemID}`;
}
// if (itemID !== undefined) {
// url += `?ParentID=${itemID}`;
// }
const response = await axios_instance.get(url, {
headers: {
"X-MediaBrowser-Token": this.apiKey,

10
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "jfstat",
"version": "1.0.0",
"version": "1.0.4.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "jfstat",
"version": "1.0.0",
"version": "1.0.4.6",
"dependencies": {
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
@@ -26,6 +26,7 @@
"concurrently": "^7.6.0",
"cors": "^2.8.5",
"crypto-js": "^4.1.1",
"file-saver": "^2.0.5",
"github-api": "^3.4.0",
"http-proxy-middleware": "^2.0.6",
"knex": "^2.4.2",
@@ -9593,6 +9594,11 @@
"webpack": "^4.0.0 || ^5.0.0"
}
},
"node_modules/file-saver": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
},
"node_modules/filelist": {
"version": "1.0.4",
"license": "Apache-2.0",

View File

@@ -1,6 +1,6 @@
{
"name": "jfstat",
"version": "1.0.4.6",
"version": "1.0.4.7",
"private": true,
"dependencies": {
"@emotion/react": "^11.10.6",
@@ -21,6 +21,7 @@
"concurrently": "^7.6.0",
"cors": "^2.8.5",
"crypto-js": "^4.1.1",
"file-saver": "^2.0.5",
"github-api": "^3.4.0",
"http-proxy-middleware": "^2.0.6",
"knex": "^2.4.2",

View File

@@ -28,6 +28,7 @@ import About from './pages/about';
import Testing from './pages/testing';
import Activity from './pages/activity';
import Statistics from './pages/statistics';
import Datadebugger from './pages/data-debugger';
function App() {
@@ -135,6 +136,7 @@ if (config && setupState===2 && token!==null){
<Route path="/activity" element={<Activity />} />
<Route path="/testing" element={<Testing />} />
<Route path="/about" element={<About />} />
<Route path="/debugger/data" element={<Datadebugger />} />
</Routes>
</main>
</div>

142
src/pages/data-debugger.js Normal file
View File

@@ -0,0 +1,142 @@
import React, { useState, useEffect } from "react";
import axios from 'axios';
import {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 { saveAs } from 'file-saver';
function Datadebugger() {
const [data, setData] = useState();
const token = localStorage.getItem('token');
useEffect(() => {
const fetchData = async () => {
try {
const libraryData = await axios.post(`/api/getLibraries`, {
itemid: undefined,
}, {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
});
setData(libraryData.data);
console.log(libraryData.data);
} catch (error) {
console.log(error);
}
};
fetchData();
const intervalId = setInterval(fetchData, 60000 * 5);
return () => clearInterval(intervalId);
}, [token]);
const handleDownload = (jsonData,filename) => {
// const jsonData = { /* Your JSON object */ };
const jsonString = JSON.stringify(jsonData);
const blob = new Blob([jsonString], { type: 'application/json' });
saveAs(blob, filename+'.json');
};
return (
<div style={{color:'white '}}>
<h1>Data Debugger</h1>
<br/>
{/* <p>{data? JSON.stringify(data):''}</p> */}
<TableContainer className='rounded-2'>
<Table aria-label="collapsible table" >
<TableHead>
<TableRow>
<TableCell>Data Type</TableCell>
<TableCell>Database Count</TableCell>
<TableCell>API Count</TableCell>
<TableCell>Difference</TableCell>
<TableCell>Export Missing Data</TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Libraries</TableCell>
<TableCell>{data ? data.existing_library_count:''}</TableCell>
<TableCell>{data ? data.api_library_count:''}</TableCell>
<TableCell>{data ? data.api_library_count-data.existing_library_count:''}</TableCell>
<TableCell>{data && data.api_library_count!==data.existing_library_count ?
<Button onClick={()=>handleDownload(data.missing_api_library_data,'MissingLibraryData')}>
Download
</Button>:
''}
</TableCell>
</TableRow>
<TableRow>
<TableCell>Library Items</TableCell>
<TableCell>{data ? data.existing_item_count:''}</TableCell>
<TableCell>{data ? data.api_item_count:''}</TableCell>
<TableCell>{data ? data.api_item_count-data.existing_item_count:''}</TableCell>
<TableCell>{data && data.api_item_count!==data.existing_item_count ?
<Button onClick={()=>handleDownload(data.missing_api_item_data,'MissingItemData')}>
Download
</Button>:
''}
</TableCell>
</TableRow>
<TableRow>
<TableCell>Seasons</TableCell>
<TableCell>{data ? data.existing_season_count:''}</TableCell>
<TableCell>{data ? data.api_season_count:''}</TableCell>
<TableCell>{data ? data.api_season_count-data.existing_season_count:''}</TableCell>
<TableCell>{data && data.api_season_count!==data.existing_season_count ?
<Button onClick={()=>handleDownload(data.missing_api_season_data,'MissingSeasonData')}>
Download
</Button>:
''}
</TableCell>
</TableRow>
<TableRow>
<TableCell>Episodes</TableCell>
<TableCell>{data ? data.existing_episode_count:''}</TableCell>
<TableCell>{data ? data.api_episode_count:''}</TableCell>
<TableCell>{data ? data.api_episode_count-data.existing_episode_count:''}</TableCell>
<TableCell>{data && data.api_episode_count!==data.existing_episode_count ?
<Button onClick={()=>handleDownload(data.missing_api_episode_data,'MissingEpisodeData')}>
Download
</Button>:
''}
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</div>
);
}
export default Datadebugger;