mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-11-03 19:07:00 -05:00 
			
		
		
		
	Update queries to account for user permissions
This commit is contained in:
		
							parent
							
								
									83d0db0607
								
							
						
					
					
						commit
						1372c24535
					
				@ -207,7 +207,7 @@ class LibraryController {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    payload.offset = payload.page * payload.limit
 | 
					    payload.offset = payload.page * payload.limit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { libraryItems, count } = await Database.models.libraryItem.getByFilterAndSort(req.library, req.user.id, payload)
 | 
					    const { libraryItems, count } = await Database.models.libraryItem.getByFilterAndSort(req.library, req.user, payload)
 | 
				
			||||||
    payload.results = libraryItems
 | 
					    payload.results = libraryItems
 | 
				
			||||||
    payload.total = count
 | 
					    payload.total = count
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -640,7 +640,7 @@ class LibraryController {
 | 
				
			|||||||
  async getUserPersonalizedShelves(req, res) {
 | 
					  async getUserPersonalizedShelves(req, res) {
 | 
				
			||||||
    const limitPerShelf = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) || 10 : 10
 | 
					    const limitPerShelf = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) || 10 : 10
 | 
				
			||||||
    const include = (req.query.include || '').split(',').map(v => v.trim().toLowerCase()).filter(v => !!v)
 | 
					    const include = (req.query.include || '').split(',').map(v => v.trim().toLowerCase()).filter(v => !!v)
 | 
				
			||||||
    const shelves = await Database.models.libraryItem.getPersonalizedShelves(req.library, req.user.id, include, limitPerShelf)
 | 
					    const shelves = await Database.models.libraryItem.getPersonalizedShelves(req.library, req.user, include, limitPerShelf)
 | 
				
			||||||
    res.json(shelves)
 | 
					    res.json(shelves)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -403,12 +403,12 @@ module.exports = (sequelize) => {
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get library items using filter and sort
 | 
					     * Get library items using filter and sort
 | 
				
			||||||
     * @param {oldLibrary} library 
 | 
					     * @param {oldLibrary} library 
 | 
				
			||||||
     * @param {string} userId 
 | 
					     * @param {oldUser} user 
 | 
				
			||||||
     * @param {object} options 
 | 
					     * @param {object} options 
 | 
				
			||||||
     * @returns {object} { libraryItems:oldLibraryItem[], count:number }
 | 
					     * @returns {object} { libraryItems:oldLibraryItem[], count:number }
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static async getByFilterAndSort(library, userId, options) {
 | 
					    static async getByFilterAndSort(library, user, options) {
 | 
				
			||||||
      const { libraryItems, count } = await libraryFilters.getFilteredLibraryItems(library, userId, options)
 | 
					      const { libraryItems, count } = await libraryFilters.getFilteredLibraryItems(library, user, options)
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        libraryItems: libraryItems.map(li => {
 | 
					        libraryItems: libraryItems.map(li => {
 | 
				
			||||||
          const oldLibraryItem = this.getOldLibraryItem(li).toJSONMinified()
 | 
					          const oldLibraryItem = this.getOldLibraryItem(li).toJSONMinified()
 | 
				
			||||||
@ -440,18 +440,18 @@ module.exports = (sequelize) => {
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get home page data personalized shelves
 | 
					     * Get home page data personalized shelves
 | 
				
			||||||
     * @param {oldLibrary} library 
 | 
					     * @param {oldLibrary} library 
 | 
				
			||||||
     * @param {string} userId 
 | 
					     * @param {oldUser} user 
 | 
				
			||||||
     * @param {string[]} include 
 | 
					     * @param {string[]} include 
 | 
				
			||||||
     * @param {number} limit 
 | 
					     * @param {number} limit 
 | 
				
			||||||
     * @returns {object[]} array of shelf objects
 | 
					     * @returns {object[]} array of shelf objects
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    static async getPersonalizedShelves(library, userId, include, limit) {
 | 
					    static async getPersonalizedShelves(library, user, include, limit) {
 | 
				
			||||||
      const fullStart = Date.now() // Used for testing load times
 | 
					      const fullStart = Date.now() // Used for testing load times
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const shelves = []
 | 
					      const shelves = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // "Continue Listening" shelf
 | 
					      // "Continue Listening" shelf
 | 
				
			||||||
      const itemsInProgressPayload = await libraryFilters.getMediaItemsInProgress(library, userId, include, limit, false)
 | 
					      const itemsInProgressPayload = await libraryFilters.getMediaItemsInProgress(library, user, include, limit, false)
 | 
				
			||||||
      if (itemsInProgressPayload.items.length) {
 | 
					      if (itemsInProgressPayload.items.length) {
 | 
				
			||||||
        shelves.push({
 | 
					        shelves.push({
 | 
				
			||||||
          id: 'continue-listening',
 | 
					          id: 'continue-listening',
 | 
				
			||||||
@ -467,7 +467,7 @@ module.exports = (sequelize) => {
 | 
				
			|||||||
      let start = Date.now()
 | 
					      let start = Date.now()
 | 
				
			||||||
      if (library.isBook) {
 | 
					      if (library.isBook) {
 | 
				
			||||||
        // "Continue Reading" shelf
 | 
					        // "Continue Reading" shelf
 | 
				
			||||||
        const ebooksInProgressPayload = await libraryFilters.getMediaItemsInProgress(library, userId, include, limit, true)
 | 
					        const ebooksInProgressPayload = await libraryFilters.getMediaItemsInProgress(library, user, include, limit, true)
 | 
				
			||||||
        if (ebooksInProgressPayload.items.length) {
 | 
					        if (ebooksInProgressPayload.items.length) {
 | 
				
			||||||
          shelves.push({
 | 
					          shelves.push({
 | 
				
			||||||
            id: 'continue-reading',
 | 
					            id: 'continue-reading',
 | 
				
			||||||
@ -482,7 +482,7 @@ module.exports = (sequelize) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        start = Date.now()
 | 
					        start = Date.now()
 | 
				
			||||||
        // "Continue Series" shelf
 | 
					        // "Continue Series" shelf
 | 
				
			||||||
        const continueSeriesPayload = await libraryFilters.getLibraryItemsContinueSeries(library, userId, include, limit)
 | 
					        const continueSeriesPayload = await libraryFilters.getLibraryItemsContinueSeries(library, user, include, limit)
 | 
				
			||||||
        if (continueSeriesPayload.libraryItems.length) {
 | 
					        if (continueSeriesPayload.libraryItems.length) {
 | 
				
			||||||
          shelves.push({
 | 
					          shelves.push({
 | 
				
			||||||
            id: 'continue-series',
 | 
					            id: 'continue-series',
 | 
				
			||||||
@ -496,7 +496,7 @@ module.exports = (sequelize) => {
 | 
				
			|||||||
        Logger.debug(`Loaded ${continueSeriesPayload.libraryItems.length} of ${continueSeriesPayload.count} items for "Continue Series" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
 | 
					        Logger.debug(`Loaded ${continueSeriesPayload.libraryItems.length} of ${continueSeriesPayload.count} items for "Continue Series" in ${((Date.now() - start) / 1000).toFixed(2)}s`)
 | 
				
			||||||
      } else if (library.isPodcast) {
 | 
					      } else if (library.isPodcast) {
 | 
				
			||||||
        // "Newest Episodes" shelf
 | 
					        // "Newest Episodes" shelf
 | 
				
			||||||
        const newestEpisodesPayload = await libraryFilters.getNewestPodcastEpisodes(library, userId, limit)
 | 
					        const newestEpisodesPayload = await libraryFilters.getNewestPodcastEpisodes(library, user, limit)
 | 
				
			||||||
        if (newestEpisodesPayload.libraryItems.length) {
 | 
					        if (newestEpisodesPayload.libraryItems.length) {
 | 
				
			||||||
          shelves.push({
 | 
					          shelves.push({
 | 
				
			||||||
            id: 'newest-episodes',
 | 
					            id: 'newest-episodes',
 | 
				
			||||||
@ -512,7 +512,7 @@ module.exports = (sequelize) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      start = Date.now()
 | 
					      start = Date.now()
 | 
				
			||||||
      // "Recently Added" shelf
 | 
					      // "Recently Added" shelf
 | 
				
			||||||
      const mostRecentPayload = await libraryFilters.getLibraryItemsMostRecentlyAdded(library, userId, include, limit)
 | 
					      const mostRecentPayload = await libraryFilters.getLibraryItemsMostRecentlyAdded(library, user, include, limit)
 | 
				
			||||||
      if (mostRecentPayload.libraryItems.length) {
 | 
					      if (mostRecentPayload.libraryItems.length) {
 | 
				
			||||||
        shelves.push({
 | 
					        shelves.push({
 | 
				
			||||||
          id: 'recently-added',
 | 
					          id: 'recently-added',
 | 
				
			||||||
@ -528,7 +528,7 @@ module.exports = (sequelize) => {
 | 
				
			|||||||
      if (library.isBook) {
 | 
					      if (library.isBook) {
 | 
				
			||||||
        start = Date.now()
 | 
					        start = Date.now()
 | 
				
			||||||
        // "Recent Series" shelf
 | 
					        // "Recent Series" shelf
 | 
				
			||||||
        const seriesMostRecentPayload = await libraryFilters.getSeriesMostRecentlyAdded(library, include, 5)
 | 
					        const seriesMostRecentPayload = await libraryFilters.getSeriesMostRecentlyAdded(library, user, include, 5)
 | 
				
			||||||
        if (seriesMostRecentPayload.series.length) {
 | 
					        if (seriesMostRecentPayload.series.length) {
 | 
				
			||||||
          shelves.push({
 | 
					          shelves.push({
 | 
				
			||||||
            id: 'recent-series',
 | 
					            id: 'recent-series',
 | 
				
			||||||
@ -543,7 +543,7 @@ module.exports = (sequelize) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        start = Date.now()
 | 
					        start = Date.now()
 | 
				
			||||||
        // "Discover" shelf
 | 
					        // "Discover" shelf
 | 
				
			||||||
        const discoverLibraryItemsPayload = await libraryFilters.getLibraryItemsToDiscover(library, userId, include, limit)
 | 
					        const discoverLibraryItemsPayload = await libraryFilters.getLibraryItemsToDiscover(library, user, include, limit)
 | 
				
			||||||
        if (discoverLibraryItemsPayload.libraryItems.length) {
 | 
					        if (discoverLibraryItemsPayload.libraryItems.length) {
 | 
				
			||||||
          shelves.push({
 | 
					          shelves.push({
 | 
				
			||||||
            id: 'discover',
 | 
					            id: 'discover',
 | 
				
			||||||
@ -559,7 +559,7 @@ module.exports = (sequelize) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      start = Date.now()
 | 
					      start = Date.now()
 | 
				
			||||||
      // "Listen Again" shelf
 | 
					      // "Listen Again" shelf
 | 
				
			||||||
      const listenAgainPayload = await libraryFilters.getMediaFinished(library, userId, include, limit, false)
 | 
					      const listenAgainPayload = await libraryFilters.getMediaFinished(library, user, include, limit, false)
 | 
				
			||||||
      if (listenAgainPayload.items.length) {
 | 
					      if (listenAgainPayload.items.length) {
 | 
				
			||||||
        shelves.push({
 | 
					        shelves.push({
 | 
				
			||||||
          id: 'listen-again',
 | 
					          id: 'listen-again',
 | 
				
			||||||
@ -575,7 +575,7 @@ module.exports = (sequelize) => {
 | 
				
			|||||||
      if (library.isBook) {
 | 
					      if (library.isBook) {
 | 
				
			||||||
        start = Date.now()
 | 
					        start = Date.now()
 | 
				
			||||||
        // "Read Again" shelf
 | 
					        // "Read Again" shelf
 | 
				
			||||||
        const readAgainPayload = await libraryFilters.getMediaFinished(library, userId, include, limit, true)
 | 
					        const readAgainPayload = await libraryFilters.getMediaFinished(library, user, include, limit, true)
 | 
				
			||||||
        if (readAgainPayload.items.length) {
 | 
					        if (readAgainPayload.items.length) {
 | 
				
			||||||
          shelves.push({
 | 
					          shelves.push({
 | 
				
			||||||
            id: 'read-again',
 | 
					            id: 'read-again',
 | 
				
			||||||
@ -590,7 +590,7 @@ module.exports = (sequelize) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        start = Date.now()
 | 
					        start = Date.now()
 | 
				
			||||||
        // "Newest Authors" shelf
 | 
					        // "Newest Authors" shelf
 | 
				
			||||||
        const newestAuthorsPayload = await libraryFilters.getNewestAuthors(library, limit)
 | 
					        const newestAuthorsPayload = await libraryFilters.getNewestAuthors(library, user, limit)
 | 
				
			||||||
        if (newestAuthorsPayload.authors.length) {
 | 
					        if (newestAuthorsPayload.authors.length) {
 | 
				
			||||||
          shelves.push({
 | 
					          shelves.push({
 | 
				
			||||||
            id: 'newest-authors',
 | 
					            id: 'newest-authors',
 | 
				
			||||||
 | 
				
			|||||||
@ -12,11 +12,11 @@ module.exports = {
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Get library items using filter and sort
 | 
					   * Get library items using filter and sort
 | 
				
			||||||
   * @param {oldLibrary} library 
 | 
					   * @param {oldLibrary} library 
 | 
				
			||||||
   * @param {string} userId 
 | 
					   * @param {oldUser} user 
 | 
				
			||||||
   * @param {object} options 
 | 
					   * @param {object} options 
 | 
				
			||||||
   * @returns {object} { libraryItems:LibraryItem[], count:number }
 | 
					   * @returns {object} { libraryItems:LibraryItem[], count:number }
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  async getFilteredLibraryItems(library, userId, options) {
 | 
					  async getFilteredLibraryItems(library, user, options) {
 | 
				
			||||||
    const { filterBy, sortBy, sortDesc, limit, offset, collapseseries, include, mediaType } = options
 | 
					    const { filterBy, sortBy, sortDesc, limit, offset, collapseseries, include, mediaType } = options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let filterValue = null
 | 
					    let filterValue = null
 | 
				
			||||||
@ -29,25 +29,25 @@ module.exports = {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (mediaType === 'book') {
 | 
					    if (mediaType === 'book') {
 | 
				
			||||||
      return libraryItemsBookFilters.getFilteredLibraryItems(library.id, userId, filterGroup, filterValue, sortBy, sortDesc, collapseseries, include, limit, offset)
 | 
					      return libraryItemsBookFilters.getFilteredLibraryItems(library.id, user, filterGroup, filterValue, sortBy, sortDesc, collapseseries, include, limit, offset)
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      return libraryItemsPodcastFilters.getFilteredLibraryItems(library.id, userId, filterGroup, filterValue, sortBy, sortDesc, include, limit, offset)
 | 
					      return libraryItemsPodcastFilters.getFilteredLibraryItems(library.id, user, filterGroup, filterValue, sortBy, sortDesc, include, limit, offset)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Get library items for continue listening & continue reading shelves
 | 
					   * Get library items for continue listening & continue reading shelves
 | 
				
			||||||
   * @param {oldLibrary} library 
 | 
					   * @param {oldLibrary} library 
 | 
				
			||||||
   * @param {string} userId 
 | 
					   * @param {oldUser} user 
 | 
				
			||||||
   * @param {string[]} include 
 | 
					   * @param {string[]} include 
 | 
				
			||||||
   * @param {number} limit 
 | 
					   * @param {number} limit 
 | 
				
			||||||
   * @param {boolean} ebook true if continue reading shelf
 | 
					   * @param {boolean} ebook true if continue reading shelf
 | 
				
			||||||
   * @returns {object} { items:LibraryItem[], count:number }
 | 
					   * @returns {object} { items:LibraryItem[], count:number }
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  async getMediaItemsInProgress(library, userId, include, limit, ebook = false) {
 | 
					  async getMediaItemsInProgress(library, user, include, limit, ebook = false) {
 | 
				
			||||||
    if (library.mediaType === 'book') {
 | 
					    if (library.mediaType === 'book') {
 | 
				
			||||||
      const filterValue = ebook ? 'ebook-in-progress' : 'audio-in-progress'
 | 
					      const filterValue = ebook ? 'ebook-in-progress' : 'audio-in-progress'
 | 
				
			||||||
      const { libraryItems, count } = await libraryItemsBookFilters.getFilteredLibraryItems(library.id, userId, 'progress', filterValue, 'progress', true, false, include, limit, 0)
 | 
					      const { libraryItems, count } = await libraryItemsBookFilters.getFilteredLibraryItems(library.id, user, 'progress', filterValue, 'progress', true, false, include, limit, 0)
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        items: libraryItems.map(li => {
 | 
					        items: libraryItems.map(li => {
 | 
				
			||||||
          const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(li).toJSONMinified()
 | 
					          const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(li).toJSONMinified()
 | 
				
			||||||
@ -59,7 +59,7 @@ module.exports = {
 | 
				
			|||||||
        count
 | 
					        count
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      const { libraryItems, count } = await libraryItemsPodcastFilters.getFilteredPodcastEpisodes(library.id, userId, 'progress', 'in-progress', 'progress', true, limit, 0)
 | 
					      const { libraryItems, count } = await libraryItemsPodcastFilters.getFilteredPodcastEpisodes(library.id, user, 'progress', 'in-progress', 'progress', true, limit, 0)
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        count,
 | 
					        count,
 | 
				
			||||||
        items: libraryItems.map(li => {
 | 
					        items: libraryItems.map(li => {
 | 
				
			||||||
@ -74,14 +74,14 @@ module.exports = {
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Get library items for most recently added shelf
 | 
					   * Get library items for most recently added shelf
 | 
				
			||||||
   * @param {oldLibrary} library 
 | 
					   * @param {oldLibrary} library 
 | 
				
			||||||
   * @param {string} userId 
 | 
					   * @param {oldUser} user 
 | 
				
			||||||
   * @param {string[]} include 
 | 
					   * @param {string[]} include 
 | 
				
			||||||
   * @param {number} limit 
 | 
					   * @param {number} limit 
 | 
				
			||||||
   * @returns {object} { libraryItems:LibraryItem[], count:number }
 | 
					   * @returns {object} { libraryItems:LibraryItem[], count:number }
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  async getLibraryItemsMostRecentlyAdded(library, userId, include, limit) {
 | 
					  async getLibraryItemsMostRecentlyAdded(library, user, include, limit) {
 | 
				
			||||||
    if (library.mediaType === 'book') {
 | 
					    if (library.mediaType === 'book') {
 | 
				
			||||||
      const { libraryItems, count } = await libraryItemsBookFilters.getFilteredLibraryItems(library.id, userId, null, null, 'addedAt', true, false, include, limit, 0)
 | 
					      const { libraryItems, count } = await libraryItemsBookFilters.getFilteredLibraryItems(library.id, user, null, null, 'addedAt', true, false, include, limit, 0)
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        libraryItems: libraryItems.map(li => {
 | 
					        libraryItems: libraryItems.map(li => {
 | 
				
			||||||
          const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(li).toJSONMinified()
 | 
					          const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(li).toJSONMinified()
 | 
				
			||||||
@ -96,7 +96,7 @@ module.exports = {
 | 
				
			|||||||
        count
 | 
					        count
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      const { libraryItems, count } = await libraryItemsPodcastFilters.getFilteredLibraryItems(library.id, userId, null, null, 'addedAt', true, include, limit, 0)
 | 
					      const { libraryItems, count } = await libraryItemsPodcastFilters.getFilteredLibraryItems(library.id, user, null, null, 'addedAt', true, include, limit, 0)
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        libraryItems: libraryItems.map(li => {
 | 
					        libraryItems: libraryItems.map(li => {
 | 
				
			||||||
          const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(li).toJSONMinified()
 | 
					          const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(li).toJSONMinified()
 | 
				
			||||||
@ -116,13 +116,13 @@ module.exports = {
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Get library items for continue series shelf
 | 
					   * Get library items for continue series shelf
 | 
				
			||||||
   * @param {string} library 
 | 
					   * @param {string} library 
 | 
				
			||||||
   * @param {string} userId 
 | 
					   * @param {oldUser} user 
 | 
				
			||||||
   * @param {string[]} include 
 | 
					   * @param {string[]} include 
 | 
				
			||||||
   * @param {number} limit 
 | 
					   * @param {number} limit 
 | 
				
			||||||
   * @returns {object} { libraryItems:LibraryItem[], count:number }
 | 
					   * @returns {object} { libraryItems:LibraryItem[], count:number }
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  async getLibraryItemsContinueSeries(library, userId, include, limit) {
 | 
					  async getLibraryItemsContinueSeries(library, user, include, limit) {
 | 
				
			||||||
    const { libraryItems, count } = await libraryItemsBookFilters.getContinueSeriesLibraryItems(library.id, userId, include, limit, 0)
 | 
					    const { libraryItems, count } = await libraryItemsBookFilters.getContinueSeriesLibraryItems(library.id, user, include, limit, 0)
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      libraryItems: libraryItems.map(li => {
 | 
					      libraryItems: libraryItems.map(li => {
 | 
				
			||||||
        const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(li).toJSONMinified()
 | 
					        const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(li).toJSONMinified()
 | 
				
			||||||
@ -141,18 +141,18 @@ module.exports = {
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Get library items or podcast episodes for the "Listen Again" or "Read Again" shelf
 | 
					   * Get library items or podcast episodes for the "Listen Again" or "Read Again" shelf
 | 
				
			||||||
   * @param {oldLibrary} library 
 | 
					   * @param {oldLibrary} library 
 | 
				
			||||||
   * @param {string} userId 
 | 
					   * @param {oldUser} user 
 | 
				
			||||||
   * @param {string[]} include 
 | 
					   * @param {string[]} include 
 | 
				
			||||||
   * @param {number} limit 
 | 
					   * @param {number} limit 
 | 
				
			||||||
   * @param {boolean} ebook true if "Read Again" shelf
 | 
					   * @param {boolean} ebook true if "Read Again" shelf
 | 
				
			||||||
   * @returns {object} { items:object[], count:number }
 | 
					   * @returns {object} { items:object[], count:number }
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  async getMediaFinished(library, userId, include, limit, ebook = false) {
 | 
					  async getMediaFinished(library, user, include, limit, ebook = false) {
 | 
				
			||||||
    if (ebook && library.mediaType !== 'book') return { items: [], count: 0 }
 | 
					    if (ebook && library.mediaType !== 'book') return { items: [], count: 0 }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (library.mediaType === 'book') {
 | 
					    if (library.mediaType === 'book') {
 | 
				
			||||||
      const filterValue = ebook ? 'ebook-finished' : 'finished'
 | 
					      const filterValue = ebook ? 'ebook-finished' : 'finished'
 | 
				
			||||||
      const { libraryItems, count } = await libraryItemsBookFilters.getFilteredLibraryItems(library.id, userId, 'progress', filterValue, 'progress', true, false, include, limit, 0)
 | 
					      const { libraryItems, count } = await libraryItemsBookFilters.getFilteredLibraryItems(library.id, user, 'progress', filterValue, 'progress', true, false, include, limit, 0)
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        items: libraryItems.map(li => {
 | 
					        items: libraryItems.map(li => {
 | 
				
			||||||
          const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(li).toJSONMinified()
 | 
					          const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(li).toJSONMinified()
 | 
				
			||||||
@ -164,7 +164,7 @@ module.exports = {
 | 
				
			|||||||
        count
 | 
					        count
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      const { libraryItems, count } = await libraryItemsPodcastFilters.getFilteredPodcastEpisodes(library.id, userId, 'progress', 'finished', 'progress', true, limit, 0)
 | 
					      const { libraryItems, count } = await libraryItemsPodcastFilters.getFilteredPodcastEpisodes(library.id, user, 'progress', 'finished', 'progress', true, limit, 0)
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        count,
 | 
					        count,
 | 
				
			||||||
        items: libraryItems.map(li => {
 | 
					        items: libraryItems.map(li => {
 | 
				
			||||||
@ -179,11 +179,12 @@ module.exports = {
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Get series for recent series shelf
 | 
					   * Get series for recent series shelf
 | 
				
			||||||
   * @param {oldLibrary} library 
 | 
					   * @param {oldLibrary} library 
 | 
				
			||||||
 | 
					   * @param {oldUser} user
 | 
				
			||||||
   * @param {string[]} include 
 | 
					   * @param {string[]} include 
 | 
				
			||||||
   * @param {number} limit 
 | 
					   * @param {number} limit 
 | 
				
			||||||
   * @returns {object} { series:oldSeries[], count:number}
 | 
					   * @returns {object} { series:oldSeries[], count:number}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  async getSeriesMostRecentlyAdded(library, include, limit) {
 | 
					  async getSeriesMostRecentlyAdded(library, user, include, limit) {
 | 
				
			||||||
    if (library.mediaType !== 'book') return { series: [], count: 0 }
 | 
					    if (library.mediaType !== 'book') return { series: [], count: 0 }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const seriesIncludes = []
 | 
					    const seriesIncludes = []
 | 
				
			||||||
@ -192,19 +193,46 @@ module.exports = {
 | 
				
			|||||||
        model: Database.models.feed
 | 
					        model: Database.models.feed
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const { rows: series, count } = await Database.models.series.findAndCountAll({
 | 
					
 | 
				
			||||||
      where: {
 | 
					    const userPermissionBookWhere = libraryItemsBookFilters.getUserPermissionBookWhereQuery(user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const seriesWhere = [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
        libraryId: library.id
 | 
					        libraryId: library.id
 | 
				
			||||||
      },
 | 
					      }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					    // Handle user permissions to only include series with at least 1 book
 | 
				
			||||||
 | 
					    // TODO: Simplify to a single query
 | 
				
			||||||
 | 
					    if (userPermissionBookWhere.bookWhere.length) {
 | 
				
			||||||
 | 
					      let attrQuery = 'SELECT count(*) FROM books b, bookSeries bs WHERE bs.seriesId = series.id AND bs.bookId = b.id'
 | 
				
			||||||
 | 
					      if (!user.canAccessExplicitContent) {
 | 
				
			||||||
 | 
					        attrQuery += ' AND b.explicit = 0'
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (!user.permissions.accessAllTags && user.itemTagsSelected.length) {
 | 
				
			||||||
 | 
					        if (user.permissions.selectedTagsNotAccessible) {
 | 
				
			||||||
 | 
					          attrQuery += ' AND (SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected)) = 0'
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          attrQuery += ' AND (SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected)) > 0'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      seriesWhere.push(Sequelize.where(Sequelize.literal(`(${attrQuery})`), {
 | 
				
			||||||
 | 
					        [Sequelize.Op.gt]: 0
 | 
				
			||||||
 | 
					      }))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { rows: series, count } = await Database.models.series.findAndCountAll({
 | 
				
			||||||
 | 
					      where: seriesWhere,
 | 
				
			||||||
      limit,
 | 
					      limit,
 | 
				
			||||||
      offset: 0,
 | 
					      offset: 0,
 | 
				
			||||||
      distinct: true,
 | 
					      distinct: true,
 | 
				
			||||||
      subQuery: false,
 | 
					      subQuery: false,
 | 
				
			||||||
 | 
					      replacements: userPermissionBookWhere.replacements,
 | 
				
			||||||
      include: [
 | 
					      include: [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          model: Database.models.bookSeries,
 | 
					          model: Database.models.bookSeries,
 | 
				
			||||||
          include: {
 | 
					          include: {
 | 
				
			||||||
            model: Database.models.book,
 | 
					            model: Database.models.book,
 | 
				
			||||||
 | 
					            where: userPermissionBookWhere.bookWhere,
 | 
				
			||||||
            include: {
 | 
					            include: {
 | 
				
			||||||
              model: Database.models.libraryItem
 | 
					              model: Database.models.libraryItem
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -255,10 +283,11 @@ module.exports = {
 | 
				
			|||||||
   * Get most recently created authors for "Newest Authors" shelf
 | 
					   * Get most recently created authors for "Newest Authors" shelf
 | 
				
			||||||
   * Author must be linked to at least 1 book
 | 
					   * Author must be linked to at least 1 book
 | 
				
			||||||
   * @param {oldLibrary} library 
 | 
					   * @param {oldLibrary} library 
 | 
				
			||||||
 | 
					   * @param {oldUser} user
 | 
				
			||||||
   * @param {number} limit 
 | 
					   * @param {number} limit 
 | 
				
			||||||
   * @returns {object} { authors:oldAuthor[], count:number }
 | 
					   * @returns {object} { authors:oldAuthor[], count:number }
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  async getNewestAuthors(library, limit) {
 | 
					  async getNewestAuthors(library, user, limit) {
 | 
				
			||||||
    if (library.mediaType !== 'book') return { authors: [], count: 0 }
 | 
					    if (library.mediaType !== 'book') return { authors: [], count: 0 }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { rows: authors, count } = await Database.models.author.findAndCountAll({
 | 
					    const { rows: authors, count } = await Database.models.author.findAndCountAll({
 | 
				
			||||||
@ -288,15 +317,15 @@ module.exports = {
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Get book library items for the "Discover" shelf
 | 
					   * Get book library items for the "Discover" shelf
 | 
				
			||||||
   * @param {oldLibrary} library 
 | 
					   * @param {oldLibrary} library 
 | 
				
			||||||
   * @param {string} userId 
 | 
					   * @param {oldUser} user 
 | 
				
			||||||
   * @param {string[]} include 
 | 
					   * @param {string[]} include 
 | 
				
			||||||
   * @param {number} limit 
 | 
					   * @param {number} limit 
 | 
				
			||||||
   * @returns {object} {libraryItems:oldLibraryItem[], count:number}
 | 
					   * @returns {object} {libraryItems:oldLibraryItem[], count:number}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  async getLibraryItemsToDiscover(library, userId, include, limit) {
 | 
					  async getLibraryItemsToDiscover(library, user, include, limit) {
 | 
				
			||||||
    if (library.mediaType !== 'book') return { libraryItems: [], count: 0 }
 | 
					    if (library.mediaType !== 'book') return { libraryItems: [], count: 0 }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { libraryItems, count } = await libraryItemsBookFilters.getDiscoverLibraryItems(library.id, userId, include, limit)
 | 
					    const { libraryItems, count } = await libraryItemsBookFilters.getDiscoverLibraryItems(library.id, user, include, limit)
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      libraryItems: libraryItems.map(li => {
 | 
					      libraryItems: libraryItems.map(li => {
 | 
				
			||||||
        const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(li).toJSONMinified()
 | 
					        const oldLibraryItem = Database.models.libraryItem.getOldLibraryItem(li).toJSONMinified()
 | 
				
			||||||
@ -312,14 +341,14 @@ module.exports = {
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Get podcast episodes most recently added
 | 
					   * Get podcast episodes most recently added
 | 
				
			||||||
   * @param {oldLibrary} library 
 | 
					   * @param {oldLibrary} library 
 | 
				
			||||||
   * @param {string} userId 
 | 
					   * @param {oldUser} user 
 | 
				
			||||||
   * @param {number} limit 
 | 
					   * @param {number} limit 
 | 
				
			||||||
   * @returns {object} {libraryItems:oldLibraryItem[], count:number}
 | 
					   * @returns {object} {libraryItems:oldLibraryItem[], count:number}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  async getNewestPodcastEpisodes(library, userId, limit) {
 | 
					  async getNewestPodcastEpisodes(library, user, limit) {
 | 
				
			||||||
    if (library.mediaType !== 'podcast') return { libraryItems: [], count: 0 }
 | 
					    if (library.mediaType !== 'podcast') return { libraryItems: [], count: 0 }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { libraryItems, count } = await libraryItemsPodcastFilters.getFilteredPodcastEpisodes(library.id, userId, null, null, 'createdAt', true, limit, 0)
 | 
					    const { libraryItems, count } = await libraryItemsPodcastFilters.getFilteredPodcastEpisodes(library.id, user, null, null, 'createdAt', true, limit, 0)
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      count,
 | 
					      count,
 | 
				
			||||||
      libraryItems: libraryItems.map(li => {
 | 
					      libraryItems: libraryItems.map(li => {
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,35 @@ const Database = require('../../Database')
 | 
				
			|||||||
const Logger = require('../../Logger')
 | 
					const Logger = require('../../Logger')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * User permissions to restrict books for explicit content & tags
 | 
				
			||||||
 | 
					   * @param {oldUser} user 
 | 
				
			||||||
 | 
					   * @returns {object} { bookWhere:Sequelize.WhereOptions, replacements:string[] }
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  getUserPermissionBookWhereQuery(user) {
 | 
				
			||||||
 | 
					    const bookWhere = []
 | 
				
			||||||
 | 
					    const replacements = {}
 | 
				
			||||||
 | 
					    if (!user.canAccessExplicitContent) {
 | 
				
			||||||
 | 
					      bookWhere.push({
 | 
				
			||||||
 | 
					        explicit: false
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!user.permissions.accessAllTags && user.itemTagsSelected.length) {
 | 
				
			||||||
 | 
					      replacements['userTagsSelected'] = user.itemTagsSelected
 | 
				
			||||||
 | 
					      if (user.permissions.selectedTagsNotAccessible) {
 | 
				
			||||||
 | 
					        bookWhere.push(Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), 0))
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        bookWhere.push(Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), {
 | 
				
			||||||
 | 
					          [Sequelize.Op.gte]: 1
 | 
				
			||||||
 | 
					        }))
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      bookWhere,
 | 
				
			||||||
 | 
					      replacements
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * When collapsing series and filtering by progress
 | 
					   * When collapsing series and filtering by progress
 | 
				
			||||||
   * different where options are required
 | 
					   * different where options are required
 | 
				
			||||||
@ -296,6 +325,7 @@ module.exports = {
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Get library items for book media type using filter and sort
 | 
					   * Get library items for book media type using filter and sort
 | 
				
			||||||
   * @param {string} libraryId 
 | 
					   * @param {string} libraryId 
 | 
				
			||||||
 | 
					   * @param {oldUser} user
 | 
				
			||||||
   * @param {[string]} filterGroup 
 | 
					   * @param {[string]} filterGroup 
 | 
				
			||||||
   * @param {[string]} filterValue 
 | 
					   * @param {[string]} filterValue 
 | 
				
			||||||
   * @param {string} sortBy 
 | 
					   * @param {string} sortBy 
 | 
				
			||||||
@ -306,7 +336,7 @@ module.exports = {
 | 
				
			|||||||
   * @param {number} offset 
 | 
					   * @param {number} offset 
 | 
				
			||||||
   * @returns {object} { libraryItems:LibraryItem[], count:number }
 | 
					   * @returns {object} { libraryItems:LibraryItem[], count:number }
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  async getFilteredLibraryItems(libraryId, userId, filterGroup, filterValue, sortBy, sortDesc, collapseseries, include, limit, offset) {
 | 
					  async getFilteredLibraryItems(libraryId, user, filterGroup, filterValue, sortBy, sortDesc, collapseseries, include, limit, offset) {
 | 
				
			||||||
    // TODO: Handle collapse sub-series
 | 
					    // TODO: Handle collapse sub-series
 | 
				
			||||||
    if (filterGroup === 'series' && collapseseries) {
 | 
					    if (filterGroup === 'series' && collapseseries) {
 | 
				
			||||||
      collapseseries = false
 | 
					      collapseseries = false
 | 
				
			||||||
@ -442,14 +472,22 @@ module.exports = {
 | 
				
			|||||||
        model: Database.models.mediaProgress,
 | 
					        model: Database.models.mediaProgress,
 | 
				
			||||||
        attributes: ['id', 'isFinished', 'currentTime', 'ebookProgress', 'updatedAt'],
 | 
					        attributes: ['id', 'isFinished', 'currentTime', 'ebookProgress', 'updatedAt'],
 | 
				
			||||||
        where: {
 | 
					        where: {
 | 
				
			||||||
          userId
 | 
					          userId: user.id
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        required: false
 | 
					        required: false
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { mediaWhere, replacements } = this.getMediaGroupQuery(filterGroup, filterValue)
 | 
					    let { mediaWhere, replacements } = this.getMediaGroupQuery(filterGroup, filterValue)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let bookWhere = Array.isArray(mediaWhere) ? mediaWhere : [mediaWhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // User permissions
 | 
				
			||||||
 | 
					    const userPermissionBookWhere = this.getUserPermissionBookWhereQuery(user)
 | 
				
			||||||
 | 
					    replacements = { ...replacements, ...userPermissionBookWhere.replacements }
 | 
				
			||||||
 | 
					    bookWhere.push(...userPermissionBookWhere.bookWhere)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Handle collapsed series
 | 
				
			||||||
    let collapseSeriesBookSeries = []
 | 
					    let collapseSeriesBookSeries = []
 | 
				
			||||||
    if (collapseseries) {
 | 
					    if (collapseseries) {
 | 
				
			||||||
      let seriesBookWhere = null
 | 
					      let seriesBookWhere = null
 | 
				
			||||||
@ -461,7 +499,7 @@ module.exports = {
 | 
				
			|||||||
          ['$books.authors.id$']: null
 | 
					          ['$books.authors.id$']: null
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        seriesBookWhere = mediaWhere
 | 
					        seriesBookWhere = bookWhere
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const bookFindOptions = {
 | 
					      const bookFindOptions = {
 | 
				
			||||||
@ -479,9 +517,11 @@ module.exports = {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
      const { booksToExclude, bookSeriesToInclude } = await this.getCollapseSeriesBooksToExclude(bookFindOptions, seriesWhere)
 | 
					      const { booksToExclude, bookSeriesToInclude } = await this.getCollapseSeriesBooksToExclude(bookFindOptions, seriesWhere)
 | 
				
			||||||
      if (booksToExclude.length) {
 | 
					      if (booksToExclude.length) {
 | 
				
			||||||
        mediaWhere['id'] = {
 | 
					        bookWhere.push({
 | 
				
			||||||
          [Sequelize.Op.notIn]: booksToExclude
 | 
					          id: {
 | 
				
			||||||
        }
 | 
					            [Sequelize.Op.notIn]: booksToExclude
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      collapseSeriesBookSeries = bookSeriesToInclude
 | 
					      collapseSeriesBookSeries = bookSeriesToInclude
 | 
				
			||||||
      if (!bookAttributes?.include) bookAttributes = { include: [] }
 | 
					      if (!bookAttributes?.include) bookAttributes = { include: [] }
 | 
				
			||||||
@ -496,7 +536,7 @@ module.exports = {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { rows: books, count } = await Database.models.book.findAndCountAll({
 | 
					    const { rows: books, count } = await Database.models.book.findAndCountAll({
 | 
				
			||||||
      where: mediaWhere,
 | 
					      where: bookWhere,
 | 
				
			||||||
      distinct: true,
 | 
					      distinct: true,
 | 
				
			||||||
      attributes: bookAttributes,
 | 
					      attributes: bookAttributes,
 | 
				
			||||||
      replacements,
 | 
					      replacements,
 | 
				
			||||||
@ -572,51 +612,13 @@ module.exports = {
 | 
				
			|||||||
   * 3. Has at least 1 unfinished book
 | 
					   * 3. Has at least 1 unfinished book
 | 
				
			||||||
   * TODO: Reduce queries
 | 
					   * TODO: Reduce queries
 | 
				
			||||||
   * @param {string} libraryId 
 | 
					   * @param {string} libraryId 
 | 
				
			||||||
   * @param {string} userId 
 | 
					   * @param {oldUser} user 
 | 
				
			||||||
   * @param {string[]} include 
 | 
					   * @param {string[]} include 
 | 
				
			||||||
   * @param {number} limit 
 | 
					   * @param {number} limit 
 | 
				
			||||||
   * @param {number} offset 
 | 
					   * @param {number} offset 
 | 
				
			||||||
   * @returns {object} { libraryItems:LibraryItem[], count:number }
 | 
					   * @returns {object} { libraryItems:LibraryItem[], count:number }
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  async getContinueSeriesLibraryItems(libraryId, userId, include, limit, offset) {
 | 
					  async getContinueSeriesLibraryItems(libraryId, user, include, limit, offset) {
 | 
				
			||||||
    // Step 1: Get all media progress for user that belongs to a series book
 | 
					 | 
				
			||||||
    const mediaProgressForUserForSeries = await Database.models.mediaProgress.findAll({
 | 
					 | 
				
			||||||
      where: {
 | 
					 | 
				
			||||||
        userId
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      include: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          model: Database.models.book,
 | 
					 | 
				
			||||||
          attributes: ['id', 'title'],
 | 
					 | 
				
			||||||
          include: {
 | 
					 | 
				
			||||||
            model: Database.models.series,
 | 
					 | 
				
			||||||
            attributes: ['id'],
 | 
					 | 
				
			||||||
            through: {
 | 
					 | 
				
			||||||
              attributes: []
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            required: true
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          required: true
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      ]
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Step 1.5: Identify the series that have at least 1 finished book and have no books in progress
 | 
					 | 
				
			||||||
    let seriesToInclude = []
 | 
					 | 
				
			||||||
    let seriesToExclude = []
 | 
					 | 
				
			||||||
    for (const prog of mediaProgressForUserForSeries) {
 | 
					 | 
				
			||||||
      const series = prog.mediaItem?.series || []
 | 
					 | 
				
			||||||
      for (const s of series) {
 | 
					 | 
				
			||||||
        if (prog.currentTime > 0 && !prog.isFinished) { // in-progress
 | 
					 | 
				
			||||||
          seriesToInclude = seriesToInclude.filter(sid => sid !== s.id)
 | 
					 | 
				
			||||||
          if (!seriesToExclude.includes(s.id)) seriesToExclude.push(s.id)
 | 
					 | 
				
			||||||
        } else if (prog.isFinished && !seriesToExclude.includes(s.id) && !seriesToInclude.includes(s.id)) { // finished
 | 
					 | 
				
			||||||
          seriesToInclude.push(s.id)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // optional include rssFeed with library item
 | 
					 | 
				
			||||||
    const libraryItemIncludes = []
 | 
					    const libraryItemIncludes = []
 | 
				
			||||||
    if (include.includes('rssfeed')) {
 | 
					    if (include.includes('rssfeed')) {
 | 
				
			||||||
      libraryItemIncludes.push({
 | 
					      libraryItemIncludes.push({
 | 
				
			||||||
@ -624,90 +626,101 @@ module.exports = {
 | 
				
			|||||||
      })
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Step 2: Get all series identified in step 1.5 and filter out series where all books are finished
 | 
					    const bookWhere = []
 | 
				
			||||||
 | 
					    // TODO: Permissions should also be applied to subqueries
 | 
				
			||||||
 | 
					    // User permissions
 | 
				
			||||||
 | 
					    const userPermissionBookWhere = this.getUserPermissionBookWhereQuery(user)
 | 
				
			||||||
 | 
					    bookWhere.push(...userPermissionBookWhere.bookWhere)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { rows: series, count } = await Database.models.series.findAndCountAll({
 | 
					    const { rows: series, count } = await Database.models.series.findAndCountAll({
 | 
				
			||||||
      where: {
 | 
					      where: [
 | 
				
			||||||
        id: {
 | 
					        {
 | 
				
			||||||
          [Sequelize.Op.in]: seriesToInclude
 | 
					          libraryId
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        '$bookSeries.book.mediaProgresses.isFinished$': {
 | 
					        // TODO: Simplify queries
 | 
				
			||||||
          [Sequelize.Op.or]: [false, null]
 | 
					        // Has at least 1 book finished
 | 
				
			||||||
 | 
					        Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM mediaProgresses mp, bookSeries bs WHERE bs.seriesId = series.id AND mp.mediaItemId = bs.bookId AND mp.userId = :userId AND mp.isFinished = 1)`), {
 | 
				
			||||||
 | 
					          [Sequelize.Op.gte]: 1
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        // Has at least 1 book not finished
 | 
				
			||||||
 | 
					        Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM bookSeries bs LEFT OUTER JOIN mediaProgresses mp ON mp.mediaItemId = bs.bookId AND mp.userId = :userId WHERE bs.seriesId = series.id AND (mp.isFinished = 0 OR mp.isFinished IS NULL))`), {
 | 
				
			||||||
 | 
					          [Sequelize.Op.gte]: 1
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        // Has no books in progress
 | 
				
			||||||
 | 
					        Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM mediaProgresses mp, bookSeries bs WHERE mp.mediaItemId = bs.bookId AND mp.userId = :userId AND bs.seriesId = series.id AND mp.isFinished = 0 AND mp.currentTime > 0)`), 0)
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      attributes: {
 | 
				
			||||||
 | 
					        include: [
 | 
				
			||||||
 | 
					          [Sequelize.literal('(SELECT max(mp.updatedAt) FROM bookSeries bs, mediaProgresses mp WHERE mp.mediaItemId = bs.bookId AND mp.userId = :userId AND bs.seriesId = series.id)'), 'recent_progress']
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      replacements: {
 | 
				
			||||||
 | 
					        userId: user.id,
 | 
				
			||||||
 | 
					        ...userPermissionBookWhere.replacements
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      include: {
 | 
				
			||||||
 | 
					        model: Database.models.bookSeries,
 | 
				
			||||||
 | 
					        attributes: ['bookId', 'sequence'],
 | 
				
			||||||
 | 
					        separate: true,
 | 
				
			||||||
 | 
					        subQuery: false,
 | 
				
			||||||
 | 
					        order: [
 | 
				
			||||||
 | 
					          [Sequelize.literal('CAST(sequence AS INTEGER) ASC NULLS LAST')]
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        where: {
 | 
				
			||||||
 | 
					          '$book.mediaProgresses.isFinished$': {
 | 
				
			||||||
 | 
					            [Sequelize.Op.or]: [null, 0]
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        include: {
 | 
				
			||||||
 | 
					          model: Database.models.book,
 | 
				
			||||||
 | 
					          where: bookWhere,
 | 
				
			||||||
 | 
					          include: [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              model: Database.models.libraryItem,
 | 
				
			||||||
 | 
					              include: libraryItemIncludes
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              model: Database.models.author,
 | 
				
			||||||
 | 
					              through: {
 | 
				
			||||||
 | 
					                attributes: []
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              model: Database.models.mediaProgress,
 | 
				
			||||||
 | 
					              where: {
 | 
				
			||||||
 | 
					                userId: user.id
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					              required: false
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          ]
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      distinct: true,
 | 
					 | 
				
			||||||
      include: [
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          model: Database.models.bookSeries,
 | 
					 | 
				
			||||||
          include: {
 | 
					 | 
				
			||||||
            model: Database.models.book,
 | 
					 | 
				
			||||||
            include: [
 | 
					 | 
				
			||||||
              {
 | 
					 | 
				
			||||||
                model: Database.models.libraryItem,
 | 
					 | 
				
			||||||
                where: {
 | 
					 | 
				
			||||||
                  libraryId
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                include: libraryItemIncludes
 | 
					 | 
				
			||||||
              },
 | 
					 | 
				
			||||||
              {
 | 
					 | 
				
			||||||
                model: Database.models.bookAuthor,
 | 
					 | 
				
			||||||
                attributes: ['authorId'],
 | 
					 | 
				
			||||||
                include: {
 | 
					 | 
				
			||||||
                  model: Database.models.author
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                separate: true
 | 
					 | 
				
			||||||
              },
 | 
					 | 
				
			||||||
              {
 | 
					 | 
				
			||||||
                model: Database.models.mediaProgress,
 | 
					 | 
				
			||||||
                where: {
 | 
					 | 
				
			||||||
                  userId
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                required: false
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
            required: true
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          required: true
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
      order: [
 | 
					      order: [
 | 
				
			||||||
        // Sort by progress most recently updated
 | 
					        [Sequelize.literal('recent_progress DESC')]
 | 
				
			||||||
        [Database.models.bookSeries, Database.models.book, Database.models.mediaProgress, 'updatedAt', 'DESC'],
 | 
					 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
 | 
					      distinct: true,
 | 
				
			||||||
      subQuery: false,
 | 
					      subQuery: false,
 | 
				
			||||||
      limit,
 | 
					      limit,
 | 
				
			||||||
      offset
 | 
					      offset
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Step 3: Map series to library items by selecting the first unfinished book in the series
 | 
					 | 
				
			||||||
    const libraryItems = series.map(s => {
 | 
					    const libraryItems = series.map(s => {
 | 
				
			||||||
      // Natural sort sequence, nulls last
 | 
					      if (!s.bookSeries.length) return null // this is only possible if user has restricted books in series
 | 
				
			||||||
      // TODO: sort in query. was unable to sort nested association with sequelize
 | 
					      const libraryItem = s.bookSeries[0].book.libraryItem.toJSON()
 | 
				
			||||||
      s.bookSeries.sort((a, b) => {
 | 
					      const book = s.bookSeries[0].book.toJSON()
 | 
				
			||||||
        if (!a.sequence) return 1
 | 
					      delete book.libraryItem
 | 
				
			||||||
        if (!b.sequence) return -1
 | 
					 | 
				
			||||||
        return a.sequence.localeCompare(b.sequence, undefined, {
 | 
					 | 
				
			||||||
          numeric: true,
 | 
					 | 
				
			||||||
          sensitivity: 'base'
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // Get first unfinished book to use
 | 
					 | 
				
			||||||
      const bookSeries = s.bookSeries.find(bs => !bs.book.mediaProgresses?.[0]?.isFinished)
 | 
					 | 
				
			||||||
      const libraryItem = bookSeries.book.libraryItem.toJSON()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      libraryItem.series = {
 | 
					      libraryItem.series = {
 | 
				
			||||||
        id: s.id,
 | 
					        id: s.id,
 | 
				
			||||||
        name: s.name,
 | 
					        name: s.name,
 | 
				
			||||||
        sequence: bookSeries.sequence
 | 
					        sequence: s.bookSeries[0].sequence
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (libraryItem.feeds?.length) {
 | 
					      if (libraryItem.feeds?.length) {
 | 
				
			||||||
        libraryItem.rssFeed = libraryItem.feeds[0]
 | 
					        libraryItem.rssFeed = libraryItem.feeds[0]
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      libraryItem.media = book
 | 
				
			||||||
      libraryItem.media = bookSeries.book
 | 
					 | 
				
			||||||
      return libraryItem
 | 
					      return libraryItem
 | 
				
			||||||
    })
 | 
					    }).filter(s => s)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      libraryItems,
 | 
					      libraryItems,
 | 
				
			||||||
      count
 | 
					      count
 | 
				
			||||||
@ -719,12 +732,14 @@ module.exports = {
 | 
				
			|||||||
   * Random selection of books that are not started
 | 
					   * Random selection of books that are not started
 | 
				
			||||||
   *  - only includes the first book of a not-started series
 | 
					   *  - only includes the first book of a not-started series
 | 
				
			||||||
   * @param {string} libraryId 
 | 
					   * @param {string} libraryId 
 | 
				
			||||||
   * @param {string} userId 
 | 
					   * @param {oldUser} user 
 | 
				
			||||||
   * @param {string[]} include 
 | 
					   * @param {string[]} include 
 | 
				
			||||||
   * @param {number} limit 
 | 
					   * @param {number} limit 
 | 
				
			||||||
   * @returns {object} {libraryItems:LibraryItem, count:number}
 | 
					   * @returns {object} {libraryItems:LibraryItem, count:number}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  async getDiscoverLibraryItems(libraryId, userId, include, limit) {
 | 
					  async getDiscoverLibraryItems(libraryId, user, include, limit) {
 | 
				
			||||||
 | 
					    const userPermissionBookWhere = this.getUserPermissionBookWhereQuery(user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Step 1: Get the first book of every series that hasnt been started yet
 | 
					    // Step 1: Get the first book of every series that hasnt been started yet
 | 
				
			||||||
    const seriesNotStarted = await Database.models.series.findAll({
 | 
					    const seriesNotStarted = await Database.models.series.findAll({
 | 
				
			||||||
      where: [
 | 
					      where: [
 | 
				
			||||||
@ -734,7 +749,8 @@ module.exports = {
 | 
				
			|||||||
        Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM bookSeries bs LEFT OUTER JOIN mediaProgresses mp ON mp.mediaItemId = bs.bookId WHERE bs.seriesId = series.id AND mp.userId = :userId AND (mp.isFinished = 1 OR mp.currentTime > 0))`), 0)
 | 
					        Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM bookSeries bs LEFT OUTER JOIN mediaProgresses mp ON mp.mediaItemId = bs.bookId WHERE bs.seriesId = series.id AND mp.userId = :userId AND (mp.isFinished = 1 OR mp.currentTime > 0))`), 0)
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
      replacements: {
 | 
					      replacements: {
 | 
				
			||||||
        userId
 | 
					        userId: user.id,
 | 
				
			||||||
 | 
					        ...userPermissionBookWhere.replacements
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      attributes: ['id'],
 | 
					      attributes: ['id'],
 | 
				
			||||||
      include: {
 | 
					      include: {
 | 
				
			||||||
@ -742,6 +758,10 @@ module.exports = {
 | 
				
			|||||||
        attributes: ['bookId', 'sequence'],
 | 
					        attributes: ['bookId', 'sequence'],
 | 
				
			||||||
        separate: true,
 | 
					        separate: true,
 | 
				
			||||||
        required: true,
 | 
					        required: true,
 | 
				
			||||||
 | 
					        include: {
 | 
				
			||||||
 | 
					          model: Database.models.book,
 | 
				
			||||||
 | 
					          where: userPermissionBookWhere.bookWhere
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        order: [
 | 
					        order: [
 | 
				
			||||||
          [Sequelize.literal('CAST(sequence AS INTEGER) ASC NULLS LAST')]
 | 
					          [Sequelize.literal('CAST(sequence AS INTEGER) ASC NULLS LAST')]
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
@ -762,22 +782,26 @@ module.exports = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Step 2: Get books not started and not in a series OR is the first book of a series not started (ordered randomly)
 | 
					    // Step 2: Get books not started and not in a series OR is the first book of a series not started (ordered randomly)
 | 
				
			||||||
    const { rows: books, count } = await Database.models.book.findAndCountAll({
 | 
					    const { rows: books, count } = await Database.models.book.findAndCountAll({
 | 
				
			||||||
      where: {
 | 
					      where: [
 | 
				
			||||||
        '$mediaProgresses.isFinished$': {
 | 
					        {
 | 
				
			||||||
          [Sequelize.Op.or]: [null, 0]
 | 
					          '$mediaProgresses.isFinished$': {
 | 
				
			||||||
        },
 | 
					            [Sequelize.Op.or]: [null, 0]
 | 
				
			||||||
        '$mediaProgresses.currentTime$': {
 | 
					          },
 | 
				
			||||||
          [Sequelize.Op.or]: [null, 0]
 | 
					          '$mediaProgresses.currentTime$': {
 | 
				
			||||||
        },
 | 
					            [Sequelize.Op.or]: [null, 0]
 | 
				
			||||||
        [Sequelize.Op.or]: [
 | 
					          },
 | 
				
			||||||
          Sequelize.where(Sequelize.literal(`(SELECT COUNT(*) FROM bookSeries bs where bs.bookId = book.id)`), 0),
 | 
					          [Sequelize.Op.or]: [
 | 
				
			||||||
          {
 | 
					            Sequelize.where(Sequelize.literal(`(SELECT COUNT(*) FROM bookSeries bs where bs.bookId = book.id)`), 0),
 | 
				
			||||||
            id: {
 | 
					            {
 | 
				
			||||||
              [Sequelize.Op.in]: booksFromSeriesToInclude
 | 
					              id: {
 | 
				
			||||||
 | 
					                [Sequelize.Op.in]: booksFromSeriesToInclude
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          ]
 | 
				
			||||||
        ]
 | 
					        },
 | 
				
			||||||
      },
 | 
					        ...userPermissionBookWhere.bookWhere
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      replacements: userPermissionBookWhere.replacements,
 | 
				
			||||||
      include: [
 | 
					      include: [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          model: Database.models.libraryItem,
 | 
					          model: Database.models.libraryItem,
 | 
				
			||||||
@ -789,7 +813,7 @@ module.exports = {
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
          model: Database.models.mediaProgress,
 | 
					          model: Database.models.mediaProgress,
 | 
				
			||||||
          where: {
 | 
					          where: {
 | 
				
			||||||
            userId
 | 
					            userId: user.id
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          required: false
 | 
					          required: false
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,34 @@ const Database = require('../../Database')
 | 
				
			|||||||
const Logger = require('../../Logger')
 | 
					const Logger = require('../../Logger')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * User permissions to restrict podcasts for explicit content & tags
 | 
				
			||||||
 | 
					   * @param {oldUser} user 
 | 
				
			||||||
 | 
					   * @returns {object} { podcastWhere:Sequelize.WhereOptions, replacements:string[] }
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  getUserPermissionPodcastWhereQuery(user) {
 | 
				
			||||||
 | 
					    const podcastWhere = []
 | 
				
			||||||
 | 
					    const replacements = {}
 | 
				
			||||||
 | 
					    if (!user.canAccessExplicitContent) {
 | 
				
			||||||
 | 
					      podcastWhere.push({
 | 
				
			||||||
 | 
					        explicit: false
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!user.permissions.accessAllTags && user.itemTagsSelected.length) {
 | 
				
			||||||
 | 
					      replacements['userTagsSelected'] = user.itemTagsSelected
 | 
				
			||||||
 | 
					      if (user.permissions.selectedTagsNotAccessible) {
 | 
				
			||||||
 | 
					        podcastWhere.push(Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), 0))
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        podcastWhere.push(Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM json_each(tags) WHERE json_valid(tags) AND json_each.value IN (:userTagsSelected))`), {
 | 
				
			||||||
 | 
					          [Sequelize.Op.gte]: 1
 | 
				
			||||||
 | 
					        }))
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      podcastWhere,
 | 
				
			||||||
 | 
					      replacements
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Get where options for Podcast model
 | 
					   * Get where options for Podcast model
 | 
				
			||||||
@ -64,6 +92,7 @@ module.exports = {
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Get library items for podcast media type using filter and sort
 | 
					   * Get library items for podcast media type using filter and sort
 | 
				
			||||||
   * @param {string} libraryId 
 | 
					   * @param {string} libraryId 
 | 
				
			||||||
 | 
					   * @param {oldUser} user
 | 
				
			||||||
   * @param {[string]} filterGroup 
 | 
					   * @param {[string]} filterGroup 
 | 
				
			||||||
   * @param {[string]} filterValue 
 | 
					   * @param {[string]} filterValue 
 | 
				
			||||||
   * @param {string} sortBy 
 | 
					   * @param {string} sortBy 
 | 
				
			||||||
@ -73,7 +102,7 @@ module.exports = {
 | 
				
			|||||||
   * @param {number} offset 
 | 
					   * @param {number} offset 
 | 
				
			||||||
   * @returns {object} { libraryItems:LibraryItem[], count:number }
 | 
					   * @returns {object} { libraryItems:LibraryItem[], count:number }
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  async getFilteredLibraryItems(libraryId, userId, filterGroup, filterValue, sortBy, sortDesc, include, limit, offset) {
 | 
					  async getFilteredLibraryItems(libraryId, user, filterGroup, filterValue, sortBy, sortDesc, include, limit, offset) {
 | 
				
			||||||
    const includeRSSFeed = include.includes('rssfeed')
 | 
					    const includeRSSFeed = include.includes('rssfeed')
 | 
				
			||||||
    const includeNumEpisodesIncomplete = include.includes('numepisodesincomplete')
 | 
					    const includeNumEpisodesIncomplete = include.includes('numepisodesincomplete')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -103,11 +132,18 @@ module.exports = {
 | 
				
			|||||||
      podcastIncludes.push([Sequelize.literal(`(SELECT count(*) FROM podcastEpisodes pe LEFT OUTER JOIN mediaProgresses mp ON mp.mediaItemId = pe.id AND mp.userId = :userId WHERE pe.podcastId = podcast.id AND (mp.isFinished = 0 OR mp.isFinished IS NULL))`), 'numEpisodesIncomplete'])
 | 
					      podcastIncludes.push([Sequelize.literal(`(SELECT count(*) FROM podcastEpisodes pe LEFT OUTER JOIN mediaProgresses mp ON mp.mediaItemId = pe.id AND mp.userId = :userId WHERE pe.podcastId = podcast.id AND (mp.isFinished = 0 OR mp.isFinished IS NULL))`), 'numEpisodesIncomplete'])
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { mediaWhere, replacements } = this.getMediaGroupQuery(filterGroup, filterValue)
 | 
					    let { mediaWhere, replacements } = this.getMediaGroupQuery(filterGroup, filterValue)
 | 
				
			||||||
    replacements.userId = userId
 | 
					    replacements.userId = user.id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const podcastWhere = []
 | 
				
			||||||
 | 
					    if (Object.keys(mediaWhere).length) podcastWhere.push(mediaWhere)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const userPermissionPodcastWhere = this.getUserPermissionPodcastWhereQuery(user)
 | 
				
			||||||
 | 
					    replacements = { ...replacements, ...userPermissionPodcastWhere.replacements }
 | 
				
			||||||
 | 
					    podcastWhere.push(...userPermissionPodcastWhere.podcastWhere)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { rows: podcasts, count } = await Database.models.podcast.findAndCountAll({
 | 
					    const { rows: podcasts, count } = await Database.models.podcast.findAndCountAll({
 | 
				
			||||||
      where: mediaWhere,
 | 
					      where: podcastWhere,
 | 
				
			||||||
      replacements,
 | 
					      replacements,
 | 
				
			||||||
      distinct: true,
 | 
					      distinct: true,
 | 
				
			||||||
      attributes: {
 | 
					      attributes: {
 | 
				
			||||||
@ -157,7 +193,7 @@ module.exports = {
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Get podcast episodes filtered and sorted
 | 
					   * Get podcast episodes filtered and sorted
 | 
				
			||||||
   * @param {string} libraryId 
 | 
					   * @param {string} libraryId 
 | 
				
			||||||
   * @param {string} userId 
 | 
					   * @param {oldUser} user 
 | 
				
			||||||
   * @param {[string]} filterGroup 
 | 
					   * @param {[string]} filterGroup 
 | 
				
			||||||
   * @param {[string]} filterValue 
 | 
					   * @param {[string]} filterValue 
 | 
				
			||||||
   * @param {string} sortBy 
 | 
					   * @param {string} sortBy 
 | 
				
			||||||
@ -166,7 +202,7 @@ module.exports = {
 | 
				
			|||||||
   * @param {number} offset 
 | 
					   * @param {number} offset 
 | 
				
			||||||
   * @returns {object} {libraryItems:LibraryItem[], count:number}
 | 
					   * @returns {object} {libraryItems:LibraryItem[], count:number}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  async getFilteredPodcastEpisodes(libraryId, userId, filterGroup, filterValue, sortBy, sortDesc, limit, offset) {
 | 
					  async getFilteredPodcastEpisodes(libraryId, user, filterGroup, filterValue, sortBy, sortDesc, limit, offset) {
 | 
				
			||||||
    if (sortBy === 'progress' && filterGroup !== 'progress') {
 | 
					    if (sortBy === 'progress' && filterGroup !== 'progress') {
 | 
				
			||||||
      Logger.warn('Cannot sort podcast episodes by progress without filtering by progress')
 | 
					      Logger.warn('Cannot sort podcast episodes by progress without filtering by progress')
 | 
				
			||||||
      sortBy = 'createdAt'
 | 
					      sortBy = 'createdAt'
 | 
				
			||||||
@ -178,7 +214,7 @@ module.exports = {
 | 
				
			|||||||
      podcastEpisodeIncludes.push({
 | 
					      podcastEpisodeIncludes.push({
 | 
				
			||||||
        model: Database.models.mediaProgress,
 | 
					        model: Database.models.mediaProgress,
 | 
				
			||||||
        where: {
 | 
					        where: {
 | 
				
			||||||
          userId
 | 
					          userId: user.id
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        attributes: ['id', 'isFinished', 'currentTime', 'updatedAt']
 | 
					        attributes: ['id', 'isFinished', 'currentTime', 'updatedAt']
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
@ -206,11 +242,15 @@ module.exports = {
 | 
				
			|||||||
      podcastEpisodeOrder.push([Sequelize.literal('mediaProgresses.updatedAt'), sortDesc ? 'DESC' : 'ASC'])
 | 
					      podcastEpisodeOrder.push([Sequelize.literal('mediaProgresses.updatedAt'), sortDesc ? 'DESC' : 'ASC'])
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const userPermissionPodcastWhere = this.getUserPermissionPodcastWhereQuery(user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { rows: podcastEpisodes, count } = await Database.models.podcastEpisode.findAndCountAll({
 | 
					    const { rows: podcastEpisodes, count } = await Database.models.podcastEpisode.findAndCountAll({
 | 
				
			||||||
      where: podcastEpisodeWhere,
 | 
					      where: podcastEpisodeWhere,
 | 
				
			||||||
 | 
					      replacements: userPermissionPodcastWhere.replacements,
 | 
				
			||||||
      include: [
 | 
					      include: [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          model: Database.models.podcast,
 | 
					          model: Database.models.podcast,
 | 
				
			||||||
 | 
					          where: userPermissionPodcastWhere.podcastWhere,
 | 
				
			||||||
          include: [
 | 
					          include: [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
              model: Database.models.libraryItem,
 | 
					              model: Database.models.libraryItem,
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user