Skip to content

Commit

Permalink
Update library collections api endpoint to use libraryItems from db
Browse files Browse the repository at this point in the history
  • Loading branch information
advplyr committed Aug 11, 2023
1 parent aac2879 commit 38029d1
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 31 deletions.
13 changes: 7 additions & 6 deletions server/controllers/CollectionController.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,23 @@ class CollectionController {
constructor() { }

async create(req, res) {
var newCollection = new Collection()
const newCollection = new Collection()
req.body.userId = req.user.id
var success = newCollection.setData(req.body)
if (!success) {
if (!newCollection.setData(req.body)) {
return res.status(500).send('Invalid collection data')
}
var jsonExpanded = newCollection.toJSONExpanded(Database.libraryItems)

const libraryItemsInCollection = await Database.models.libraryItem.getForCollection(newCollection)
const jsonExpanded = newCollection.toJSONExpanded(libraryItemsInCollection)
await Database.createCollection(newCollection)
SocketAuthority.emitter('collection_added', jsonExpanded)
res.json(jsonExpanded)
}

async findAll(req, res) {
const collections = await Database.models.collection.getOldCollections()
const collectionsExpanded = await Database.models.collection.getOldCollectionsJsonExpanded(req.user)
res.json({
collections: collections.map(c => c.toJSONExpanded(Database.libraryItems))
collections: collectionsExpanded
})
}

Expand Down
47 changes: 28 additions & 19 deletions server/controllers/LibraryController.js
Original file line number Diff line number Diff line change
Expand Up @@ -535,8 +535,6 @@ class LibraryController {

// api/libraries/:id/collections
async getCollectionsForLibrary(req, res) {
const libraryItems = req.libraryItems

const include = (req.query.include || '').split(',').map(v => v.trim().toLowerCase()).filter(v => !!v)

const payload = {
Expand All @@ -551,23 +549,8 @@ class LibraryController {
include: include.join(',')
}

const collectionsForLibrary = await Database.models.collection.getAllForLibrary(req.library.id)

let collections = await Promise.all(collectionsForLibrary.map(async c => {
const expanded = c.toJSONExpanded(libraryItems, payload.minified)

// If all books restricted to user in this collection then hide this collection
if (!expanded.books.length && c.books.length) return null

if (include.includes('rssfeed')) {
const feedData = await this.rssFeedManager.findFeedForEntityId(c.id)
expanded.rssFeed = feedData?.toJSONMinified() || null
}

return expanded
}))

collections = collections.filter(c => !!c)
// TODO: Create paginated queries
let collections = await Database.models.collection.getOldCollectionsJsonExpanded(req.user, req.library.id, include)

payload.total = collections.length

Expand Down Expand Up @@ -964,6 +947,12 @@ class LibraryController {
res.send(opmlText)
}

/**
* TODO: Replace with middlewareNew
* @param {*} req
* @param {*} res
* @param {*} next
*/
async middleware(req, res, next) {
if (!req.user.checkCanAccessLibrary(req.params.id)) {
Logger.warn(`[LibraryController] Library ${req.params.id} not accessible to user ${req.user.username}`)
Expand All @@ -980,5 +969,25 @@ class LibraryController {
})
next()
}

/**
* Middleware that is not using libraryItems from memory
* @param {*} req
* @param {*} res
* @param {*} next
*/
async middlewareNew(req, res, next) {
if (!req.user.checkCanAccessLibrary(req.params.id)) {
Logger.warn(`[LibraryController] Library ${req.params.id} not accessible to user ${req.user.username}`)
return res.sendStatus(403)
}

const library = await Database.models.library.getOldById(req.params.id)
if (!library) {
return res.status(404).send('Library not found')
}
req.library = library
next()
}
}
module.exports = new LibraryController()
101 changes: 101 additions & 0 deletions server/models/Collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ const { areEquivalent } = require('../utils/index')

module.exports = (sequelize) => {
class Collection extends Model {
/**
* Get all old collections
* @returns {Promise<oldCollection[]>}
*/
static async getOldCollections() {
const collections = await this.findAll({
include: {
Expand All @@ -16,6 +20,103 @@ module.exports = (sequelize) => {
return collections.map(c => this.getOldCollection(c))
}

/**
* Get all old collections toJSONExpanded, items filtered for user permissions
* @param {[oldUser]} user
* @param {[string]} libraryId
* @param {[string[]]} include
* @returns {Promise<object[]>} oldCollection.toJSONExpanded
*/
static async getOldCollectionsJsonExpanded(user, libraryId, include) {
let collectionWhere = null
if (libraryId) {
collectionWhere = {
libraryId
}
}

// Optionally include rssfeed for collection
const collectionIncludes = []
if (include.includes('rssfeed')) {
collectionIncludes.push({
model: sequelize.models.feed
})
}

const collections = await this.findAll({
where: collectionWhere,
include: [
{
model: sequelize.models.book,
include: [
{
model: sequelize.models.libraryItem
},
{
model: sequelize.models.author,
through: {
attributes: []
}
},
{
model: sequelize.models.series,
through: {
attributes: ['sequence']
}
},

]
},
...collectionIncludes
],
order: [[sequelize.models.book, sequelize.models.collectionBook, 'order', 'ASC']]
})
// TODO: Handle user permission restrictions on initial query
return collections.map(c => {
const oldCollection = this.getOldCollection(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
}) || []

// Map to library items
const libraryItems = books.map(b => {
const libraryItem = b.libraryItem
delete b.libraryItem
libraryItem.media = b
return sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
})

// Users with restricted permissions will not see this collection
if (!books.length && oldCollection.books.length) {
return null
}

const collectionExpanded = oldCollection.toJSONExpanded(libraryItems)

// Map feed if found
if (c.feeds?.length) {
collectionExpanded.rssFeed = sequelize.models.feed.getOldFeed(c.feeds[0])
}

return collectionExpanded
}).filter(c => c)
}

/**
* Get old collection from Collection
* @param {Collection} collectionExpanded
* @returns {oldCollection}
*/
static getOldCollection(collectionExpanded) {
const libraryItemIds = collectionExpanded.books?.map(b => b.libraryItem?.id || null).filter(lid => lid) || []
return new oldCollection({
Expand Down
5 changes: 5 additions & 0 deletions server/models/Feed.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ module.exports = (sequelize) => {
return feeds.map(f => this.getOldFeed(f))
}

/**
* Get old feed from Feed and optionally Feed with FeedEpisodes
* @param {Feed} feedExpanded
* @returns {oldFeed}
*/
static getOldFeed(feedExpanded) {
const episodes = feedExpanded.feedEpisodes?.map((feedEpisode) => feedEpisode.getOldEpisode())
return new oldFeed({
Expand Down
12 changes: 11 additions & 1 deletion server/models/LibraryItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -661,13 +661,23 @@ module.exports = (sequelize) => {
* Get book library items for author, optional use user permissions
* @param {oldAuthor} author
* @param {[oldUser]} user
* @returns {oldLibraryItem[]}
* @returns {Promise<oldLibraryItem[]>}
*/
static async getForAuthor(author, user = null) {
const { libraryItems } = await libraryFilters.getLibraryItemsForAuthor(author, user, undefined, undefined)
return libraryItems.map(li => this.getOldLibraryItem(li))
}

/**
* Get book library items in a collection
* @param {oldCollection} collection
* @returns {Promise<oldLibraryItem[]>}
*/
static async getForCollection(collection) {
const libraryItems = await libraryFilters.getLibraryItemsForCollection(collection)
return libraryItems.map(li => this.getOldLibraryItem(li))
}

getMedia(options) {
if (!this.mediaType) return Promise.resolve(null)
const mixinMethodName = `get${sequelize.uppercaseFirst(this.mediaType)}`
Expand Down
6 changes: 3 additions & 3 deletions server/routers/ApiRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,16 @@ class ApiRouter {
this.router.patch('/libraries/:id', LibraryController.middleware.bind(this), LibraryController.update.bind(this))
this.router.delete('/libraries/:id', LibraryController.middleware.bind(this), LibraryController.delete.bind(this))

this.router.get('/libraries/:id/items2', LibraryController.middleware.bind(this), LibraryController.getLibraryItemsNew.bind(this))
this.router.get('/libraries/:id/items2', LibraryController.middlewareNew.bind(this), LibraryController.getLibraryItemsNew.bind(this))
this.router.get('/libraries/:id/items', LibraryController.middleware.bind(this), LibraryController.getLibraryItems.bind(this))
this.router.delete('/libraries/:id/issues', LibraryController.middleware.bind(this), LibraryController.removeLibraryItemsWithIssues.bind(this))
this.router.get('/libraries/:id/episode-downloads', LibraryController.middleware.bind(this), LibraryController.getEpisodeDownloadQueue.bind(this))
this.router.get('/libraries/:id/series', LibraryController.middleware.bind(this), LibraryController.getAllSeriesForLibrary.bind(this))
this.router.get('/libraries/:id/series/:seriesId', LibraryController.middleware.bind(this), LibraryController.getSeriesForLibrary.bind(this))
this.router.get('/libraries/:id/collections', LibraryController.middleware.bind(this), LibraryController.getCollectionsForLibrary.bind(this))
this.router.get('/libraries/:id/collections', LibraryController.middlewareNew.bind(this), LibraryController.getCollectionsForLibrary.bind(this))
this.router.get('/libraries/:id/playlists', LibraryController.middleware.bind(this), LibraryController.getUserPlaylistsForLibrary.bind(this))
this.router.get('/libraries/:id/albums', LibraryController.middleware.bind(this), LibraryController.getAlbumsForLibrary.bind(this))
this.router.get('/libraries/:id/personalized2', LibraryController.middleware.bind(this), LibraryController.getUserPersonalizedShelves.bind(this))
this.router.get('/libraries/:id/personalized2', LibraryController.middlewareNew.bind(this), LibraryController.getUserPersonalizedShelves.bind(this))
this.router.get('/libraries/:id/personalized', LibraryController.middleware.bind(this), LibraryController.getLibraryUserPersonalizedOptimal.bind(this))
this.router.get('/libraries/:id/filterdata', LibraryController.middleware.bind(this), LibraryController.getLibraryFilterData.bind(this))
this.router.get('/libraries/:id/search', LibraryController.middleware.bind(this), LibraryController.search.bind(this))
Expand Down
11 changes: 10 additions & 1 deletion server/utils/queries/libraryFilters.js
Original file line number Diff line number Diff line change
Expand Up @@ -365,13 +365,22 @@ module.exports = {
* @param {[oldUser]} user
* @param {number} limit
* @param {number} offset
* @returns {object} { libraryItems:LibraryItem[], count:number }
* @returns {Promise<object>} { libraryItems:LibraryItem[], count:number }
*/
async getLibraryItemsForAuthor(author, user, limit, offset) {
const { libraryItems, count } = await libraryItemsBookFilters.getFilteredLibraryItems(author.libraryId, user, 'authors', author.id, 'addedAt', true, false, [], limit, offset)
return {
count,
libraryItems
}
},

/**
* Get book library items in a collection
* @param {oldCollection} collection
* @returns {Promise<LibraryItem[]>}
*/
getLibraryItemsForCollection(collection) {
return libraryItemsBookFilters.getLibraryItemsForCollection(collection)
}
}
45 changes: 44 additions & 1 deletion server/utils/queries/libraryItemsBookFilters.js
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ module.exports = {
replacements,
benchmark: true,
logging: (sql, timeMs) => {
console.log(`[Query] Elapsed ${timeMs}ms.`)
console.log(`[Query] Elapsed ${timeMs}ms`)
},
include: [
{
Expand Down Expand Up @@ -870,5 +870,48 @@ module.exports = {
libraryItems,
count
}
},

/**
* Get book library items in a collection
* @param {oldCollection} collection
* @returns {Promise<LibraryItem[]>}
*/
async getLibraryItemsForCollection(collection) {
if (!collection?.books?.length) {
Logger.error(`[libraryItemsBookFilters] Invalid collection`, collection)
return []
}
const books = await Database.models.book.findAll({
where: {
id: {
[Sequelize.Op.in]: collection.books
}
},
include: [
{
model: Database.models.libraryItem
},
{
model: sequelize.models.author,
through: {
attributes: []
}
},
{
model: sequelize.models.series,
through: {
attributes: ['sequence']
}
}
]
})

return books.map((book) => {
const libraryItem = book.libraryItem
delete book.libraryItem
libraryItem.media = book
return libraryItem
})
}
}

0 comments on commit 38029d1

Please sign in to comment.