This commit is contained in:
Conor Bronsdon 2026-07-10 06:51:23 +02:00 committed by GitHub
commit 1aee2a1a1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 117 additions and 5 deletions

View file

@ -830,7 +830,7 @@ class LibraryController {
}
// TODO: Create paginated queries
let collections = await Database.collectionModel.getOldCollectionsJsonExpanded(req.user, req.library.id, include)
let collections = await Database.collectionModel.getOldCollectionsJsonExpanded(req.user, req.library.id, include, payload.minified)
payload.total = collections.length

View file

@ -29,9 +29,10 @@ class Collection extends Model {
* @param {import('./User')} user
* @param {string} [libraryId]
* @param {string[]} [include]
* @param {boolean} [minified=false]
* @async
*/
static async getOldCollectionsJsonExpanded(user, libraryId, include) {
static async getOldCollectionsJsonExpanded(user, libraryId, include, minified = false) {
let collectionWhere = null
if (libraryId) {
collectionWhere = {
@ -98,7 +99,7 @@ class Collection extends Model {
this.books = books
const collectionExpanded = c.toOldJSONExpanded()
const collectionExpanded = c.toOldJSONExpanded(minified)
// Map feed if found
if (c.feeds?.length) {
@ -272,7 +273,11 @@ class Collection extends Model {
}
}
toOldJSONExpanded() {
/**
* @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')
}
@ -282,7 +287,7 @@ class Collection extends Model {
const libraryItem = book.libraryItem
delete book.libraryItem
libraryItem.media = book
return libraryItem.toOldJSONExpanded()
return minified ? libraryItem.toOldJSONMinified() : libraryItem.toOldJSONExpanded()
})
return json

View file

@ -0,0 +1,107 @@
const { expect } = require('chai')
const { Sequelize } = require('sequelize')
const Database = require('../../../server/Database')
describe('Collection', () => {
/** @type {string} */
let libraryId
/** @type {string} */
let collectionId
beforeEach(async () => {
global.ServerSettings = {}
Database.sequelize = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false })
Database.sequelize.uppercaseFirst = (str) => (str ? `${str[0].toUpperCase()}${str.substr(1)}` : '')
await Database.buildModels()
const library = await Database.libraryModel.create({ name: 'Test Library', mediaType: 'book' })
libraryId = library.id
const libraryFolder = await Database.libraryFolderModel.create({ path: '/test', libraryId: library.id })
// Build a book with heavy media: many audio files + chapters
const audioFiles = []
const chapters = []
for (let i = 0; i < 30; i++) {
audioFiles.push({
index: i + 1,
ino: `ino-${i}`,
duration: 600,
exclude: false,
mimeType: 'audio/mpeg',
metadata: { filename: `track-${i}.mp3`, ext: '.mp3', path: `/test/book/track-${i}.mp3`, relPath: `track-${i}.mp3`, size: 12345678 }
})
chapters.push({ id: i, start: i * 600, end: (i + 1) * 600, title: `Chapter ${i + 1}` })
}
const book = await Database.bookModel.create({ title: 'Heavy Book', audioFiles, tags: [], narrators: [], genres: [], chapters })
// libraryItem carries a large libraryFiles array (dropped by the minified form)
const libraryFiles = []
for (let i = 0; i < 30; i++) {
libraryFiles.push({
ino: `lf-${i}`,
metadata: { filename: `track-${i}.mp3`, ext: '.mp3', path: `/test/book/track-${i}.mp3`, relPath: `track-${i}.mp3`, size: 12345678, mtimeMs: 0, ctimeMs: 0, birthtimeMs: 0 }
})
}
await Database.libraryItemModel.create({ libraryFiles, mediaId: book.id, mediaType: 'book', libraryId: library.id, libraryFolderId: libraryFolder.id })
const collection = await Database.collectionModel.create({ name: 'Test Collection', libraryId: library.id })
collectionId = collection.id
await Database.collectionBookModel.create({ collectionId: collection.id, bookId: book.id, order: 1 })
})
afterEach(async () => {
await Database.sequelize.close()
})
describe('getOldCollectionsJsonExpanded', () => {
it('returns fully expanded book objects when minified is not requested', async () => {
const collections = await Database.collectionModel.getOldCollectionsJsonExpanded(null, libraryId, [], false)
expect(collections).to.have.lengthOf(1)
const book = collections[0].books[0]
// Library item keeps the full libraryFiles array
expect(book).to.have.property('libraryFiles')
expect(book.libraryFiles).to.be.an('array').with.lengthOf(30)
// Media keeps the heavy arrays
expect(book.media).to.have.property('audioFiles').that.is.an('array').with.lengthOf(30)
expect(book.media).to.have.property('chapters').that.is.an('array').with.lengthOf(30)
expect(book.media).to.have.property('tracks').that.is.an('array').with.lengthOf(30)
})
it('returns minified book objects that omit libraryFiles and heavy media when minified is requested', async () => {
const collections = await Database.collectionModel.getOldCollectionsJsonExpanded(null, libraryId, [], true)
expect(collections).to.have.lengthOf(1)
const book = collections[0].books[0]
// Library item drops libraryFiles in favor of a numFiles count
expect(book).to.not.have.property('libraryFiles')
expect(book).to.have.property('numFiles', 30)
// Media drops the heavy arrays in favor of counts
expect(book.media).to.not.have.property('audioFiles')
expect(book.media).to.not.have.property('chapters')
expect(book.media).to.not.have.property('tracks')
expect(book.media).to.have.property('numAudioFiles', 30)
expect(book.media).to.have.property('numChapters', 30)
expect(book.media).to.have.property('numTracks', 30)
// Essential display fields survive minification
expect(book.media).to.have.property('coverPath')
expect(book.media).to.have.property('duration')
expect(book.media.metadata).to.have.property('title', 'Heavy Book')
})
it('produces a materially smaller payload when minified', async () => {
const expanded = await Database.collectionModel.getOldCollectionsJsonExpanded(null, libraryId, [], false)
const minified = await Database.collectionModel.getOldCollectionsJsonExpanded(null, libraryId, [], true)
const expandedSize = JSON.stringify(expanded).length
const minifiedSize = JSON.stringify(minified).length
expect(minifiedSize).to.be.lessThan(expandedSize * 0.5)
})
})
})