mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-04-19 04:05:55 -04:00
Create new StatsController and move year in review stats endpoint
This commit is contained in:
parent
f853cff920
commit
4fb5330308
3 changed files with 128 additions and 3 deletions
75
server/controllers/StatsController.js
Normal file
75
server/controllers/StatsController.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
const { Request, Response, NextFunction } = require('express')
|
||||
const Logger = require('../Logger')
|
||||
|
||||
const adminStats = require('../utils/queries/adminStats')
|
||||
|
||||
/**
|
||||
* @typedef RequestUserObject
|
||||
* @property {import('../models/User')} user
|
||||
*
|
||||
* @typedef {Request & RequestUserObject} RequestWithUser
|
||||
*/
|
||||
|
||||
class StatsController {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* GET: /api/stats/server
|
||||
* Currently not in use
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getServerStats(req, res) {
|
||||
Logger.debug('[StatsController] getServerStats')
|
||||
const totalSize = await adminStats.getTotalSize()
|
||||
const numAudioFiles = await adminStats.getNumAudioFiles()
|
||||
|
||||
res.json({
|
||||
books: {
|
||||
...totalSize.books,
|
||||
numAudioFiles: numAudioFiles.numBookAudioFiles
|
||||
},
|
||||
podcasts: {
|
||||
...totalSize.podcasts,
|
||||
numAudioFiles: numAudioFiles.numPodcastAudioFiles
|
||||
},
|
||||
total: {
|
||||
...totalSize.total,
|
||||
numAudioFiles: numAudioFiles.numAudioFiles
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* GET: /api/stats/year/:year
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getAdminStatsForYear(req, res) {
|
||||
const year = Number(req.params.year)
|
||||
if (isNaN(year) || year < 2000 || year > 9999) {
|
||||
Logger.error(`[StatsController] Invalid year "${year}"`)
|
||||
return res.status(400).send('Invalid year')
|
||||
}
|
||||
const stats = await adminStats.getStatsForYear(year)
|
||||
res.json(stats)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
* @param {NextFunction} next
|
||||
*/
|
||||
async middleware(req, res, next) {
|
||||
if (!req.user.isAdminOrUp) {
|
||||
Logger.error(`[StatsController] Non-root user "${req.user.username}" attempted to access stats route`)
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
}
|
||||
module.exports = new StatsController()
|
|
@ -33,8 +33,7 @@ const RSSFeedController = require('../controllers/RSSFeedController')
|
|||
const CustomMetadataProviderController = require('../controllers/CustomMetadataProviderController')
|
||||
const MiscController = require('../controllers/MiscController')
|
||||
const ShareController = require('../controllers/ShareController')
|
||||
|
||||
const { getTitleIgnorePrefix } = require('../utils/index')
|
||||
const StatsController = require('../controllers/StatsController')
|
||||
|
||||
class ApiRouter {
|
||||
constructor(Server) {
|
||||
|
@ -320,6 +319,12 @@ class ApiRouter {
|
|||
this.router.post('/share/mediaitem', ShareController.createMediaItemShare.bind(this))
|
||||
this.router.delete('/share/mediaitem/:id', ShareController.deleteMediaItemShare.bind(this))
|
||||
|
||||
//
|
||||
// Stats Routes
|
||||
//
|
||||
this.router.get('/stats/year/:year', StatsController.getAdminStatsForYear.bind(this))
|
||||
this.router.get('/stats/server', StatsController.getServerStats.bind(this))
|
||||
|
||||
//
|
||||
// Misc Routes
|
||||
//
|
||||
|
@ -338,7 +343,6 @@ class ApiRouter {
|
|||
this.router.get('/auth-settings', MiscController.getAuthSettings.bind(this))
|
||||
this.router.patch('/auth-settings', MiscController.updateAuthSettings.bind(this))
|
||||
this.router.post('/watcher/update', MiscController.updateWatchedPath.bind(this))
|
||||
this.router.get('/stats/year/:year', MiscController.getAdminStatsForYear.bind(this))
|
||||
this.router.get('/logger-data', MiscController.getLoggerData.bind(this))
|
||||
}
|
||||
|
||||
|
|
|
@ -167,5 +167,51 @@ module.exports = {
|
|||
topNarrators,
|
||||
topGenres
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get total file size and number of items for books and podcasts
|
||||
*
|
||||
* @typedef {Object} SizeObject
|
||||
* @property {number} totalSize
|
||||
* @property {number} numItems
|
||||
*
|
||||
* @returns {Promise<{books: SizeObject, podcasts: SizeObject, total: SizeObject}}>}
|
||||
*/
|
||||
async getTotalSize() {
|
||||
const [mediaTypeStats] = await Database.sequelize.query(`SELECT li.mediaType, SUM(li.size) AS totalSize, COUNT(*) AS numItems FROM libraryItems li group by li.mediaType;`)
|
||||
const bookStats = mediaTypeStats.find((m) => m.mediaType === 'book')
|
||||
const podcastStats = mediaTypeStats.find((m) => m.mediaType === 'podcast')
|
||||
|
||||
return {
|
||||
books: {
|
||||
totalSize: bookStats?.totalSize || 0,
|
||||
numItems: bookStats?.numItems || 0
|
||||
},
|
||||
podcasts: {
|
||||
totalSize: podcastStats?.totalSize || 0,
|
||||
numItems: podcastStats?.numItems || 0
|
||||
},
|
||||
total: {
|
||||
totalSize: (bookStats?.totalSize || 0) + (podcastStats?.totalSize || 0),
|
||||
numItems: (bookStats?.numItems || 0) + (podcastStats?.numItems || 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get total number of audio files for books and podcasts
|
||||
*
|
||||
* @returns {Promise<{numBookAudioFiles: number, numPodcastAudioFiles: number, numAudioFiles: number}>}
|
||||
*/
|
||||
async getNumAudioFiles() {
|
||||
const [numBookAudioFilesRow] = await Database.sequelize.query(`SELECT SUM(json_array_length(b.audioFiles)) AS numAudioFiles FROM books b;`)
|
||||
const numBookAudioFiles = numBookAudioFilesRow[0]?.numAudioFiles || 0
|
||||
const numPodcastAudioFiles = await Database.podcastEpisodeModel.count()
|
||||
return {
|
||||
numBookAudioFiles,
|
||||
numPodcastAudioFiles,
|
||||
numAudioFiles: numBookAudioFiles + numPodcastAudioFiles
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue