diff --git a/Dockerfile-arm b/Dockerfile-arm new file mode 100644 index 0000000..bbe909f --- /dev/null +++ b/Dockerfile-arm @@ -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"] diff --git a/backend/api.js b/backend/api.js index cac26bc..cf2b337 100644 --- a/backend/api.js +++ b/backend/api.js @@ -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); + } +}); + + diff --git a/backend/sync.js b/backend/sync.js index 2dace3b..9b2ed37 100644 --- a/backend/sync.js +++ b/backend/sync.js @@ -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, diff --git a/package-lock.json b/package-lock.json index 08a3779..ba21e3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index f0c7c5b..dbf792a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/App.js b/src/App.js index 41900e0..7082e6e 100644 --- a/src/App.js +++ b/src/App.js @@ -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){ } /> } /> } /> + } /> diff --git a/src/pages/data-debugger.js b/src/pages/data-debugger.js new file mode 100644 index 0000000..ccc4b20 --- /dev/null +++ b/src/pages/data-debugger.js @@ -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 ( +
+

Data Debugger

+
+ {/*

{data? JSON.stringify(data):''}

*/} + + + + + + Data Type + Database Count + API Count + Difference + Export Missing Data + + + + + + Libraries + {data ? data.existing_library_count:''} + {data ? data.api_library_count:''} + {data ? data.api_library_count-data.existing_library_count:''} + {data && data.api_library_count!==data.existing_library_count ? + : + ''} + + + + + Library Items + {data ? data.existing_item_count:''} + {data ? data.api_item_count:''} + {data ? data.api_item_count-data.existing_item_count:''} + {data && data.api_item_count!==data.existing_item_count ? + : + ''} + + + + + Seasons + {data ? data.existing_season_count:''} + {data ? data.api_season_count:''} + {data ? data.api_season_count-data.existing_season_count:''} + {data && data.api_season_count!==data.existing_season_count ? + : + ''} + + + + + Episodes + {data ? data.existing_episode_count:''} + {data ? data.api_episode_count:''} + {data ? data.api_episode_count-data.existing_episode_count:''} + {data && data.api_episode_count!==data.existing_episode_count ? + : + ''} + + + + +
+
+ +
+ + ); +} + +export default Datadebugger;