mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-12 12:21:34 +00:00
The GET /api/libraries/:id/collections endpoint captured the minified flag on the response payload but never passed it down to the book serialization, so each book was always returned fully expanded with its complete libraryFiles array and heavy media fields (audioFiles, chapters, tracks). For libraries with multi-file audiobooks this produced responses of tens of megabytes even when minified=1 was requested. Thread the minified flag through getOldCollectionsJsonExpanded and Collection.toOldJSONExpanded so that, when requested, books use the existing library item minified form. Non-minified requests are unchanged. Fixes #5170
297 lines
7.3 KiB
JavaScript
297 lines
7.3 KiB
JavaScript
const { DataTypes, Model, Sequelize } = require('sequelize')
|
|
|
|
class Collection extends Model {
|
|
constructor(values, options) {
|
|
super(values, options)
|
|
|
|
/** @type {UUIDV4} */
|
|
this.id
|
|
/** @type {string} */
|
|
this.name
|
|
/** @type {string} */
|
|
this.description
|
|
/** @type {UUIDV4} */
|
|
this.libraryId
|
|
/** @type {Date} */
|
|
this.updatedAt
|
|
/** @type {Date} */
|
|
this.createdAt
|
|
|
|
// Expanded properties
|
|
|
|
/** @type {import('./Book').BookExpandedWithLibraryItem[]} - only set when expanded */
|
|
this.books
|
|
}
|
|
|
|
/**
|
|
* Get all toOldJSONExpanded, items filtered for user permissions
|
|
*
|
|
* @param {import('./User')} user
|
|
* @param {string} [libraryId]
|
|
* @param {string[]} [include]
|
|
* @param {boolean} [minified=false]
|
|
* @async
|
|
*/
|
|
static async getOldCollectionsJsonExpanded(user, libraryId, include, minified = false) {
|
|
let collectionWhere = null
|
|
if (libraryId) {
|
|
collectionWhere = {
|
|
libraryId
|
|
}
|
|
}
|
|
|
|
// Optionally include rssfeed for collection
|
|
const collectionIncludes = []
|
|
if (include?.includes('rssfeed')) {
|
|
collectionIncludes.push({
|
|
model: this.sequelize.models.feed
|
|
})
|
|
}
|
|
|
|
const collections = await this.findAll({
|
|
where: collectionWhere,
|
|
include: [
|
|
{
|
|
model: this.sequelize.models.book,
|
|
include: [
|
|
{
|
|
model: this.sequelize.models.libraryItem
|
|
},
|
|
{
|
|
model: this.sequelize.models.author,
|
|
through: {
|
|
attributes: []
|
|
}
|
|
},
|
|
{
|
|
model: this.sequelize.models.series,
|
|
through: {
|
|
attributes: ['sequence']
|
|
}
|
|
}
|
|
]
|
|
},
|
|
...collectionIncludes
|
|
],
|
|
order: [[this.sequelize.models.book, this.sequelize.models.collectionBook, 'order', 'ASC']]
|
|
})
|
|
// TODO: Handle user permission restrictions on initial query
|
|
return collections
|
|
.map((c) => {
|
|
// Filter books using user permissions
|
|
const books =
|
|
c.books?.filter((b) => {
|
|
if (user) {
|
|
if (b.tags?.length && !user.checkCanAccessLibraryItemWithTags(b.tags)) {
|
|
return false
|
|
}
|
|
if (b.explicit === true && !user.canAccessExplicitContent) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}) || []
|
|
|
|
// Users with restricted permissions will not see this collection
|
|
if (!books.length && c.books.length) {
|
|
return null
|
|
}
|
|
|
|
this.books = books
|
|
|
|
const collectionExpanded = c.toOldJSONExpanded(minified)
|
|
|
|
// Map feed if found
|
|
if (c.feeds?.length) {
|
|
collectionExpanded.rssFeed = c.feeds[0].toOldJSON()
|
|
}
|
|
|
|
return collectionExpanded
|
|
})
|
|
.filter((c) => c)
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string} collectionId
|
|
* @returns {Promise<Collection>}
|
|
*/
|
|
static async getExpandedById(collectionId) {
|
|
return this.findByPk(collectionId, {
|
|
include: [
|
|
{
|
|
model: this.sequelize.models.book,
|
|
include: [
|
|
{
|
|
model: this.sequelize.models.libraryItem
|
|
},
|
|
{
|
|
model: this.sequelize.models.author,
|
|
through: {
|
|
attributes: []
|
|
}
|
|
},
|
|
{
|
|
model: this.sequelize.models.series,
|
|
through: {
|
|
attributes: ['sequence']
|
|
}
|
|
}
|
|
]
|
|
}
|
|
],
|
|
order: [[this.sequelize.models.book, this.sequelize.models.collectionBook, 'order', 'ASC']]
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Remove all collections belonging to library
|
|
* @param {string} libraryId
|
|
* @returns {Promise<number>} number of collections destroyed
|
|
*/
|
|
static async removeAllForLibrary(libraryId) {
|
|
if (!libraryId) return 0
|
|
return this.destroy({
|
|
where: {
|
|
libraryId
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Initialize model
|
|
* @param {import('../Database').sequelize} sequelize
|
|
*/
|
|
static init(sequelize) {
|
|
super.init(
|
|
{
|
|
id: {
|
|
type: DataTypes.UUID,
|
|
defaultValue: DataTypes.UUIDV4,
|
|
primaryKey: true
|
|
},
|
|
name: DataTypes.STRING,
|
|
description: DataTypes.TEXT
|
|
},
|
|
{
|
|
sequelize,
|
|
modelName: 'collection'
|
|
}
|
|
)
|
|
|
|
const { library } = sequelize.models
|
|
|
|
library.hasMany(Collection)
|
|
Collection.belongsTo(library)
|
|
}
|
|
|
|
/**
|
|
* Get all books in collection expanded with library item
|
|
*
|
|
* @returns {Promise<import('./Book').BookExpandedWithLibraryItem[]>}
|
|
*/
|
|
getBooksExpandedWithLibraryItem() {
|
|
return this.getBooks({
|
|
include: [
|
|
{
|
|
model: this.sequelize.models.libraryItem
|
|
},
|
|
{
|
|
model: this.sequelize.models.author,
|
|
through: {
|
|
attributes: []
|
|
}
|
|
},
|
|
{
|
|
model: this.sequelize.models.series,
|
|
through: {
|
|
attributes: ['sequence']
|
|
}
|
|
}
|
|
],
|
|
order: [Sequelize.literal('`collectionBook.order` ASC')]
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Get toOldJSONExpanded, items filtered for user permissions
|
|
*
|
|
* @param {import('./User')|null} user
|
|
* @param {string[]} [include]
|
|
* @async
|
|
*/
|
|
async getOldJsonExpanded(user, include) {
|
|
this.books = await this.getBooksExpandedWithLibraryItem()
|
|
|
|
// Filter books using user permissions
|
|
// TODO: Handle user permission restrictions on initial query
|
|
if (user) {
|
|
const books = this.books.filter((b) => {
|
|
if (b.tags?.length && !user.checkCanAccessLibraryItemWithTags(b.tags)) {
|
|
return false
|
|
}
|
|
if (b.explicit === true && !user.canAccessExplicitContent) {
|
|
return false
|
|
}
|
|
return true
|
|
})
|
|
|
|
// Users with restricted permissions will not see this collection
|
|
if (!books.length && this.books.length) {
|
|
return null
|
|
}
|
|
|
|
this.books = books
|
|
}
|
|
|
|
const collectionExpanded = this.toOldJSONExpanded()
|
|
|
|
if (include?.includes('rssfeed')) {
|
|
const feeds = await this.getFeeds()
|
|
if (feeds?.length) {
|
|
collectionExpanded.rssFeed = feeds[0].toOldJSON()
|
|
}
|
|
}
|
|
|
|
return collectionExpanded
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {string[]} [libraryItemIds=[]]
|
|
* @returns
|
|
*/
|
|
toOldJSON(libraryItemIds = []) {
|
|
return {
|
|
id: this.id,
|
|
libraryId: this.libraryId,
|
|
name: this.name,
|
|
description: this.description,
|
|
books: [...libraryItemIds],
|
|
lastUpdate: this.updatedAt.valueOf(),
|
|
createdAt: this.createdAt.valueOf()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {boolean} [minified=false] When true, books use the minified library item serialization
|
|
* (omits libraryFiles and heavy media fields like chapters/tracks/audioFiles)
|
|
*/
|
|
toOldJSONExpanded(minified = false) {
|
|
if (!this.books) {
|
|
throw new Error('Books are required to expand Collection')
|
|
}
|
|
|
|
const json = this.toOldJSON()
|
|
json.books = this.books.map((book) => {
|
|
const libraryItem = book.libraryItem
|
|
delete book.libraryItem
|
|
libraryItem.media = book
|
|
return minified ? libraryItem.toOldJSONMinified() : libraryItem.toOldJSONExpanded()
|
|
})
|
|
|
|
return json
|
|
}
|
|
}
|
|
|
|
module.exports = Collection
|