Update library stats API route to load from db

This commit is contained in:
advplyr 2023-08-19 16:53:33 -05:00
parent ff0d6326d3
commit 332078e6c1
8 changed files with 308 additions and 52 deletions

View file

@ -17,6 +17,7 @@ const naturalSort = createNewSortInstance({
const Database = require('../Database')
const libraryFilters = require('../utils/queries/libraryFilters')
const libraryItemsPodcastFilters = require('../utils/queries/libraryItemsPodcastFilters')
const authorFilters = require('../utils/queries/authorFilters')
class LibraryController {
constructor() { }
@ -809,23 +810,44 @@ class LibraryController {
res.json(matches)
}
/**
* GET: /api/libraries/:id/stats
* Get stats for library
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async stats(req, res) {
var libraryItems = req.libraryItems
var authorsWithCount = libraryHelpers.getAuthorsWithCount(libraryItems)
var genresWithCount = libraryHelpers.getGenresWithCount(libraryItems)
var durationStats = libraryHelpers.getItemDurationStats(libraryItems)
var sizeStats = libraryHelpers.getItemSizeStats(libraryItems)
var stats = {
totalItems: libraryItems.length,
totalAuthors: Object.keys(authorsWithCount).length,
totalGenres: Object.keys(genresWithCount).length,
totalDuration: durationStats.totalDuration,
longestItems: durationStats.longestItems,
numAudioTracks: durationStats.numAudioTracks,
totalSize: libraryHelpers.getLibraryItemsTotalSize(libraryItems),
largestItems: sizeStats.largestItems,
authorsWithCount,
genresWithCount
const stats = {
largestItems: await libraryItemFilters.getLargestItems(req.library.id, 10)
}
if (req.library.isBook) {
const authors = await authorFilters.getAuthorsWithCount(req.library.id)
const genres = await libraryItemsBookFilters.getGenresWithCount(req.library.id)
const bookStats = await libraryItemsBookFilters.getBookLibraryStats(req.library.id)
const longestBooks = await libraryItemsBookFilters.getLongestBooks(req.library.id, 10)
stats.totalAuthors = authors.length
stats.authorsWithCount = authors
stats.totalGenres = genres.length
stats.genresWithCount = genres
stats.totalItems = bookStats.totalItems
stats.longestItems = longestBooks
stats.totalSize = bookStats.totalSize
stats.totalDuration = bookStats.totalDuration
stats.numAudioTracks = bookStats.numAudioFiles
} else {
const genres = await libraryItemsPodcastFilters.getGenresWithCount(req.library.id)
const podcastStats = await libraryItemsPodcastFilters.getPodcastLibraryStats(req.library.id)
const longestPodcasts = await libraryItemsPodcastFilters.getLongestPodcasts(req.library.id, 10)
stats.totalGenres = genres.length
stats.genresWithCount = genres
stats.totalItems = podcastStats.totalItems
stats.longestItems = longestPodcasts
stats.totalSize = podcastStats.totalSize
stats.totalDuration = podcastStats.totalDuration
stats.numAudioTracks = podcastStats.numAudioFiles
}
res.json(stats)
}

View file

@ -87,7 +87,7 @@ class ApiRouter {
this.router.get('/libraries/:id/personalized', LibraryController.middleware.bind(this), LibraryController.getLibraryUserPersonalizedOptimal.bind(this))
this.router.get('/libraries/:id/filterdata', LibraryController.middlewareNew.bind(this), LibraryController.getLibraryFilterData.bind(this))
this.router.get('/libraries/:id/search', LibraryController.middlewareNew.bind(this), LibraryController.search.bind(this))
this.router.get('/libraries/:id/stats', LibraryController.middleware.bind(this), LibraryController.stats.bind(this))
this.router.get('/libraries/:id/stats', LibraryController.middlewareNew.bind(this), LibraryController.stats.bind(this))
this.router.get('/libraries/:id/authors', LibraryController.middlewareNew.bind(this), LibraryController.getAuthors.bind(this))
this.router.get('/libraries/:id/narrators', LibraryController.middlewareNew.bind(this), LibraryController.getNarrators.bind(this))
this.router.patch('/libraries/:id/narrators/:narratorId', LibraryController.middlewareNew.bind(this), LibraryController.updateNarrator.bind(this))

View file

@ -0,0 +1,69 @@
const Sequelize = require('sequelize')
const Logger = require('../../Logger')
const Database = require('../../Database')
module.exports = {
/**
* Get authors with count of num books
* @param {string} libraryId
* @returns {{id:string, name:string, count:number}}
*/
async getAuthorsWithCount(libraryId) {
const authors = await Database.authorModel.findAll({
where: [
{
libraryId
},
Sequelize.where(Sequelize.literal('count'), {
[Sequelize.Op.gt]: 0
})
],
attributes: [
'id',
'name',
[Sequelize.literal('(SELECT count(*) FROM bookAuthors ba WHERE ba.authorId = author.id)'), 'count']
],
order: [
['count', 'DESC']
]
})
return authors.map(au => {
return {
id: au.id,
name: au.name,
count: au.dataValues.count
}
})
},
/**
* Search authors
* @param {string} libraryId
* @param {string} query
* @returns {object[]} oldAuthor with numBooks
*/
async search(libraryId, query) {
const authors = await Database.authorModel.findAll({
where: {
name: {
[Sequelize.Op.substring]: query
},
libraryId
},
attributes: {
include: [
[Sequelize.literal('(SELECT count(*) FROM bookAuthors ba WHERE ba.authorId = author.id)'), 'numBooks']
]
},
limit,
offset
})
const authorMatches = []
for (const author of authors) {
const oldAuthor = author.getOldAuthor().toJSON()
oldAuthor.numBooks = author.dataValues.numBooks
authorMatches.push(oldAuthor)
}
return authorMatches
}
}

View file

@ -180,5 +180,41 @@ module.exports = {
} else {
return libraryItemsPodcastFilters.search(oldUser, oldLibrary, query, limit, 0)
}
},
/**
* Get largest items in library
* @param {string} libraryId
* @param {number} limit
* @returns {Promise<{ id:string, title:string, size:number }[]>}
*/
async getLargestItems(libraryId, limit) {
const libraryItems = await Database.libraryItemModel.findAll({
attributes: ['id', 'mediaId', 'mediaType', 'size'],
where: {
libraryId
},
include: [
{
model: Database.bookModel,
attributes: ['id', 'title']
},
{
model: Database.podcastModel,
attributes: ['id', 'title']
}
],
order: [
['size', 'DESC']
],
limit
})
return libraryItems.map(libraryItem => {
return {
id: libraryItem.id,
title: libraryItem.media.title,
size: libraryItem.size
}
})
}
}

View file

@ -1,6 +1,7 @@
const Sequelize = require('sequelize')
const Database = require('../../Database')
const Logger = require('../../Logger')
const authorFilters = require('./authorFilters')
module.exports = {
/**
@ -1098,27 +1099,7 @@ module.exports = {
}
// Search authors
const authors = await Database.authorModel.findAll({
where: {
name: {
[Sequelize.Op.substring]: query
},
libraryId: oldLibrary.id
},
attributes: {
include: [
[Sequelize.literal('(SELECT count(*) FROM bookAuthors ba WHERE ba.authorId = author.id)'), 'numBooks']
]
},
limit,
offset
})
const authorMatches = []
for (const author of authors) {
const oldAuthor = author.getOldAuthor().toJSON()
oldAuthor.numBooks = author.dataValues.numBooks
authorMatches.push(oldAuthor)
}
const authorMatches = await authorFilters.search(oldLibrary.id, query)
return {
book: itemMatches,
@ -1127,5 +1108,71 @@ module.exports = {
series: seriesMatches,
authors: authorMatches
}
},
/**
* Genres with num books
* @param {string} libraryId
* @returns {{genre:string, count:number}[]}
*/
async getGenresWithCount(libraryId) {
const genres = []
const [genreResults] = await Database.sequelize.query(`SELECT value, count(*) AS numItems FROM books b, libraryItems li, json_each(b.genres) WHERE json_valid(b.genres) AND b.id = li.mediaId AND li.libraryId = :libraryId GROUP BY value ORDER BY numItems DESC;`, {
replacements: {
libraryId
},
raw: true
})
for (const row of genreResults) {
genres.push({
genre: row.value,
count: row.numItems
})
}
return genres
},
/**
* Get stats for book library
* @param {string} libraryId
* @returns {Promise<{ totalSize:number, totalDuration:number, numAudioFiles:number, totalItems:number}>}
*/
async getBookLibraryStats(libraryId) {
const [statResults] = await Database.sequelize.query(`SELECT SUM(li.size) AS totalSize, SUM(b.duration) AS totalDuration, SUM(json_array_length(b.audioFiles)) AS numAudioFiles, COUNT(*) AS totalItems FROM libraryItems li, books b WHERE b.id = li.mediaId AND li.libraryId = :libraryId;`, {
replacements: {
libraryId
}
})
return statResults[0]
},
/**
* Get longest books in library
* @param {string} libraryId
* @param {number} limit
* @returns {Promise<{ id:string, title:string, duration:number }[]>}
*/
async getLongestBooks(libraryId, limit) {
const books = await Database.bookModel.findAll({
attributes: ['id', 'title', 'duration'],
include: {
model: Database.libraryItemModel,
attributes: ['id', 'libraryId'],
where: {
libraryId
}
},
order: [
['duration', 'DESC']
],
limit
})
return books.map(book => {
return {
id: book.libraryItem.id,
title: book.title,
duration: book.duration
}
})
}
}

View file

@ -455,5 +455,75 @@ module.exports = {
})
return episodeResults
},
/**
* Get stats for podcast library
* @param {string} libraryId
* @returns {Promise<{ totalSize:number, totalDuration:number, numAudioFiles:number, totalItems:number}>}
*/
async getPodcastLibraryStats(libraryId) {
const [statResults] = await Database.sequelize.query(`SELECT SUM(json_extract(pe.audioFile, '$.duration')) AS totalDuration, SUM(li.size) AS totalSize, COUNT(DISTINCT(li.id)) AS totalItems, COUNT(pe.id) AS numAudioFiles FROM libraryItems li, podcasts p LEFT OUTER JOIN podcastEpisodes pe ON pe.podcastId = p.id WHERE p.id = li.mediaId AND li.libraryId = :libraryId;`, {
replacements: {
libraryId
}
})
return statResults[0]
},
/**
* Genres with num podcasts
* @param {string} libraryId
* @returns {{genre:string, count:number}[]}
*/
async getGenresWithCount(libraryId) {
const genres = []
const [genreResults] = await Database.sequelize.query(`SELECT value, count(*) AS numItems FROM podcasts p, libraryItems li, json_each(p.genres) WHERE json_valid(p.genres) AND p.id = li.mediaId AND li.libraryId = :libraryId GROUP BY value ORDER BY numItems DESC;`, {
replacements: {
libraryId
},
raw: true
})
for (const row of genreResults) {
genres.push({
genre: row.value,
count: row.numItems
})
}
return genres
},
/**
* Get longest podcasts in library
* @param {string} libraryId
* @param {number} limit
* @returns {Promise<{ id:string, title:string, duration:number }[]>}
*/
async getLongestPodcasts(libraryId, limit) {
const podcasts = await Database.podcastModel.findAll({
attributes: [
'id',
'title',
[Sequelize.literal(`(SELECT SUM(json_extract(pe.audioFile, '$.duration')) FROM podcastEpisodes pe WHERE pe.podcastId = podcast.id)`), 'duration']
],
include: {
model: Database.libraryItemModel,
attributes: ['id', 'libraryId'],
where: {
libraryId
}
},
order: [
['duration', 'DESC']
],
limit
})
return podcasts.map(podcast => {
return {
id: podcast.libraryItem.id,
title: podcast.title,
duration: podcast.dataValues.duration
}
})
}
}