mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-11-04 03:17:00 -05:00 
			
		
		
		
	Fix:Clean user data on server start removing invalid media progress items
This commit is contained in:
		
							parent
							
								
									9ee6eaade9
								
							
						
					
					
						commit
						ac30a971c5
					
				@ -187,6 +187,11 @@ export default {
 | 
				
			|||||||
          this.$toast.error('Failed to start scan')
 | 
					          this.$toast.error('Failed to start scan')
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    userUpdated(user) {
 | 
				
			||||||
 | 
					      if (user.seriesHideFromContinueListening && user.seriesHideFromContinueListening.length) {
 | 
				
			||||||
 | 
					        this.removeAllSeriesFromContinueSeries(user.seriesHideFromContinueListening)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    libraryItemAdded(libraryItem) {
 | 
					    libraryItemAdded(libraryItem) {
 | 
				
			||||||
      console.log('libraryItem added', libraryItem)
 | 
					      console.log('libraryItem added', libraryItem)
 | 
				
			||||||
      // TODO: Check if libraryItem would be on this shelf
 | 
					      // TODO: Check if libraryItem would be on this shelf
 | 
				
			||||||
@ -244,23 +249,16 @@ export default {
 | 
				
			|||||||
        this.libraryItemUpdated(li)
 | 
					        this.libraryItemUpdated(li)
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    seriesUpdated(series) {
 | 
					    removeAllSeriesFromContinueSeries(seriesIds) {
 | 
				
			||||||
      if (series.hideFromHome) {
 | 
					 | 
				
			||||||
      this.shelves.forEach((shelf) => {
 | 
					      this.shelves.forEach((shelf) => {
 | 
				
			||||||
        if (shelf.type == 'book' && shelf.id == 'continue-series') {
 | 
					        if (shelf.type == 'book' && shelf.id == 'continue-series') {
 | 
				
			||||||
          // Filter out series books from continue series shelf
 | 
					          // Filter out series books from continue series shelf
 | 
				
			||||||
          shelf.entities = shelf.entities.filter((ent) => {
 | 
					          shelf.entities = shelf.entities.filter((ent) => {
 | 
				
			||||||
              if (ent.media.metadata.series && ent.media.metadata.series.id == series.id) return false
 | 
					            if (ent.media.metadata.series && seriesIds.includes(ent.media.metadata.series.id)) return false
 | 
				
			||||||
            return true
 | 
					            return true
 | 
				
			||||||
          })
 | 
					          })
 | 
				
			||||||
          } else if (shelf.type == 'series') {
 | 
					 | 
				
			||||||
            // Filter out series from series shelf
 | 
					 | 
				
			||||||
            shelf.entities = shelf.entities.filter((ent) => {
 | 
					 | 
				
			||||||
              return ent.id != series.id
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    authorUpdated(author) {
 | 
					    authorUpdated(author) {
 | 
				
			||||||
      this.shelves.forEach((shelf) => {
 | 
					      this.shelves.forEach((shelf) => {
 | 
				
			||||||
@ -288,7 +286,7 @@ export default {
 | 
				
			|||||||
      this.$store.commit('user/addSettingsListener', { id: 'bookshelf', meth: this.settingsUpdated })
 | 
					      this.$store.commit('user/addSettingsListener', { id: 'bookshelf', meth: this.settingsUpdated })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (this.$root.socket) {
 | 
					      if (this.$root.socket) {
 | 
				
			||||||
        this.$root.socket.on('series_updated', this.seriesUpdated)
 | 
					        this.$root.socket.on('user_updated', this.userUpdated)
 | 
				
			||||||
        this.$root.socket.on('author_updated', this.authorUpdated)
 | 
					        this.$root.socket.on('author_updated', this.authorUpdated)
 | 
				
			||||||
        this.$root.socket.on('author_removed', this.authorRemoved)
 | 
					        this.$root.socket.on('author_removed', this.authorRemoved)
 | 
				
			||||||
        this.$root.socket.on('item_updated', this.libraryItemUpdated)
 | 
					        this.$root.socket.on('item_updated', this.libraryItemUpdated)
 | 
				
			||||||
@ -304,7 +302,7 @@ export default {
 | 
				
			|||||||
      this.$store.commit('user/removeSettingsListener', 'bookshelf')
 | 
					      this.$store.commit('user/removeSettingsListener', 'bookshelf')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (this.$root.socket) {
 | 
					      if (this.$root.socket) {
 | 
				
			||||||
        this.$root.socket.off('series_updated', this.seriesUpdated)
 | 
					        this.$root.socket.off('user_updated', this.userUpdated)
 | 
				
			||||||
        this.$root.socket.off('author_updated', this.authorUpdated)
 | 
					        this.$root.socket.off('author_updated', this.authorUpdated)
 | 
				
			||||||
        this.$root.socket.off('author_removed', this.authorRemoved)
 | 
					        this.$root.socket.off('author_removed', this.authorRemoved)
 | 
				
			||||||
        this.$root.socket.off('item_updated', this.libraryItemUpdated)
 | 
					        this.$root.socket.off('item_updated', this.libraryItemUpdated)
 | 
				
			||||||
 | 
				
			|||||||
@ -28,7 +28,6 @@
 | 
				
			|||||||
            <span class="font-mono">{{ numShowing }}</span>
 | 
					            <span class="font-mono">{{ numShowing }}</span>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div class="flex-grow" />
 | 
					          <div class="flex-grow" />
 | 
				
			||||||
          <ui-btn v-if="seriesHideFromHome" :loading="processingSeriesHideFromHome" color="primary" small class="mr-2" @click="showSeriesOnHome">Show on Home</ui-btn>
 | 
					 | 
				
			||||||
          <ui-btn color="primary" small :loading="processingSeries" class="flex items-center" @click="markSeriesFinished">
 | 
					          <ui-btn color="primary" small :loading="processingSeries" class="flex items-center" @click="markSeriesFinished">
 | 
				
			||||||
            <div class="h-5 w-5">
 | 
					            <div class="h-5 w-5">
 | 
				
			||||||
              <svg v-if="isSeriesFinished" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="rgb(63, 181, 68)">
 | 
					              <svg v-if="isSeriesFinished" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="rgb(63, 181, 68)">
 | 
				
			||||||
@ -95,8 +94,7 @@ export default {
 | 
				
			|||||||
      keywordTimeout: null,
 | 
					      keywordTimeout: null,
 | 
				
			||||||
      processingSeries: false,
 | 
					      processingSeries: false,
 | 
				
			||||||
      processingIssues: false,
 | 
					      processingIssues: false,
 | 
				
			||||||
      processingAuthors: false,
 | 
					      processingAuthors: false
 | 
				
			||||||
      processingSeriesHideFromHome: false
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  computed: {
 | 
					  computed: {
 | 
				
			||||||
@ -152,9 +150,6 @@ export default {
 | 
				
			|||||||
    seriesName() {
 | 
					    seriesName() {
 | 
				
			||||||
      return this.selectedSeries ? this.selectedSeries.name : null
 | 
					      return this.selectedSeries ? this.selectedSeries.name : null
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    seriesHideFromHome() {
 | 
					 | 
				
			||||||
      return this.selectedSeries ? this.selectedSeries.hideFromHome : null
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    seriesProgress() {
 | 
					    seriesProgress() {
 | 
				
			||||||
      return this.selectedSeries ? this.selectedSeries.progress : null
 | 
					      return this.selectedSeries ? this.selectedSeries.progress : null
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -176,23 +171,6 @@ export default {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
    async showSeriesOnHome() {
 | 
					 | 
				
			||||||
      this.processingSeriesHideFromHome = true
 | 
					 | 
				
			||||||
      const seriesId = this.selectedSeries.id
 | 
					 | 
				
			||||||
      this.$axios
 | 
					 | 
				
			||||||
        .$patch(`/api/series/${seriesId}`, { hideFromHome: false })
 | 
					 | 
				
			||||||
        .then((data) => {
 | 
					 | 
				
			||||||
          console.log('Updated series', data)
 | 
					 | 
				
			||||||
          this.$toast.success('Series updated successfully')
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        .catch((error) => {
 | 
					 | 
				
			||||||
          console.error('Failed to update series', error)
 | 
					 | 
				
			||||||
          this.$toast.error('Failed to update series')
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
        .finally(() => {
 | 
					 | 
				
			||||||
          this.processingSeriesHideFromHome = false
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    async matchAllAuthors() {
 | 
					    async matchAllAuthors() {
 | 
				
			||||||
      this.processingAuthors = true
 | 
					      this.processingAuthors = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -411,10 +411,10 @@ export default {
 | 
				
			|||||||
          text: 'Re-Scan'
 | 
					          text: 'Re-Scan'
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (this.userIsAdminOrUp && this.series && this.bookMount) {
 | 
					      if (this.series && this.bookMount) {
 | 
				
			||||||
        items.push({
 | 
					        items.push({
 | 
				
			||||||
          func: 'hideSeriesFromHome',
 | 
					          func: 'hideSeriesFromContinueListening',
 | 
				
			||||||
          text: 'Hide Series from Home'
 | 
					          text: 'Hide Series from Continue Series'
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return items
 | 
					      return items
 | 
				
			||||||
@ -595,17 +595,17 @@ export default {
 | 
				
			|||||||
      // More menu func
 | 
					      // More menu func
 | 
				
			||||||
      this.store.commit('showEditModalOnTab', { libraryItem: this.libraryItem, tab: 'match' })
 | 
					      this.store.commit('showEditModalOnTab', { libraryItem: this.libraryItem, tab: 'match' })
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    hideSeriesFromHome() {
 | 
					    hideSeriesFromContinueListening() {
 | 
				
			||||||
      const axios = this.$axios || this.$nuxt.$axios
 | 
					      const axios = this.$axios || this.$nuxt.$axios
 | 
				
			||||||
      this.processing = true
 | 
					      this.processing = true
 | 
				
			||||||
      axios
 | 
					      axios
 | 
				
			||||||
        .$patch(`/api/series/${this.series.id}`, { hideFromHome: true })
 | 
					        .$post(`/api/me/series/${this.series.id}/hide`)
 | 
				
			||||||
        .then((data) => {
 | 
					        .then((data) => {
 | 
				
			||||||
          console.log('Series updated', data)
 | 
					          console.log('User updated', data)
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .catch((error) => {
 | 
					        .catch((error) => {
 | 
				
			||||||
          console.error('Failed to hide series from home', error)
 | 
					          console.error('Failed to hide series from home', error)
 | 
				
			||||||
          this.$toast.error('Failed to update series')
 | 
					          this.$toast.error('Failed to update user')
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .finally(() => {
 | 
					        .finally(() => {
 | 
				
			||||||
          this.processing = false
 | 
					          this.processing = false
 | 
				
			||||||
 | 
				
			|||||||
@ -145,7 +145,7 @@ class Server {
 | 
				
			|||||||
      await this.auth.initTokenSecret()
 | 
					      await this.auth.initTokenSecret()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await this.checkUserMediaProgress() // Remove invalid user item progress
 | 
					    await this.cleanUserData() // Remove invalid user item progress
 | 
				
			||||||
    await this.purgeMetadata() // Remove metadata folders without library item
 | 
					    await this.purgeMetadata() // Remove metadata folders without library item
 | 
				
			||||||
    await this.playbackSessionManager.removeInvalidSessions()
 | 
					    await this.playbackSessionManager.removeInvalidSessions()
 | 
				
			||||||
    await this.cacheManager.ensureCachePaths()
 | 
					    await this.cacheManager.ensureCachePaths()
 | 
				
			||||||
@ -368,23 +368,39 @@ class Server {
 | 
				
			|||||||
    return purged
 | 
					    return purged
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Remove user media progress entries that dont have a library item
 | 
					  // Remove user media progress with items that no longer exist & remove seriesHideFrom that no longer exist
 | 
				
			||||||
  // TODO: Check podcast episode exists still
 | 
					  async cleanUserData() {
 | 
				
			||||||
  async checkUserMediaProgress() {
 | 
					 | 
				
			||||||
    for (let i = 0; i < this.db.users.length; i++) {
 | 
					    for (let i = 0; i < this.db.users.length; i++) {
 | 
				
			||||||
      var _user = this.db.users[i]
 | 
					      var _user = this.db.users[i]
 | 
				
			||||||
      if (_user.mediaProgress) {
 | 
					      var hasUpdated = false
 | 
				
			||||||
        var itemProgressIdsToRemove = _user.mediaProgress.map(lip => lip.id).filter(lipId => !this.db.libraryItems.find(_li => _li.id == lipId))
 | 
					      if (_user.mediaProgress.length) {
 | 
				
			||||||
        if (itemProgressIdsToRemove.length) {
 | 
					        const lengthBefore = _user.mediaProgress.length
 | 
				
			||||||
          Logger.debug(`[Server] Found ${itemProgressIdsToRemove.length} media progress data to remove from user ${_user.username}`)
 | 
					        _user.mediaProgress = _user.mediaProgress.filter(mp => {
 | 
				
			||||||
          for (const lipId of itemProgressIdsToRemove) {
 | 
					          const libraryItem = this.db.libraryItems.find(li => li.id === mp.libraryItemId)
 | 
				
			||||||
            _user.removeMediaProgress(lipId)
 | 
					          if (!libraryItem) return false
 | 
				
			||||||
 | 
					          if (mp.episodeId && (libraryItem.mediaType !== 'podcast' || !libraryItem.media.checkHasEpisode(mp.episodeId))) return false // Episode not found
 | 
				
			||||||
 | 
					          return true
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (lengthBefore > _user.mediaProgress.length) {
 | 
				
			||||||
 | 
					          Logger.debug(`[Server] Removing ${_user.mediaProgress.length - lengthBefore} media progress data from user ${_user.username}`)
 | 
				
			||||||
 | 
					          hasUpdated = true
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (_user.seriesHideFromContinueListening.length) {
 | 
				
			||||||
 | 
					        _user.seriesHideFromContinueListening = _user.seriesHideFromContinueListening.filter(seriesId => {
 | 
				
			||||||
 | 
					          if (!this.db.series.some(se => se.id === seriesId)) { // Series removed
 | 
				
			||||||
 | 
					            hasUpdated = true
 | 
				
			||||||
 | 
					            return false
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          return true
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (hasUpdated) {
 | 
				
			||||||
        await this.db.updateEntity('user', _user)
 | 
					        await this.db.updateEntity('user', _user)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // First time login rate limit is hit
 | 
					  // First time login rate limit is hit
 | 
				
			||||||
  loginLimitReached(req, res, options) {
 | 
					  loginLimitReached(req, res, options) {
 | 
				
			||||||
 | 
				
			|||||||
@ -276,5 +276,21 @@ class MeController {
 | 
				
			|||||||
      libraryItems: itemsInProgress
 | 
					      libraryItems: itemsInProgress
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // GET: api/me/series/:id/hide
 | 
				
			||||||
 | 
					  async hideSeriesFromContinueListening(req, res) {
 | 
				
			||||||
 | 
					    const series = this.db.series.find(se => se.id === req.params.id)
 | 
				
			||||||
 | 
					    if (!series) {
 | 
				
			||||||
 | 
					      Logger.error(`[MeController] hideSeriesFromContinueListening: Series ${req.params.id} not found`)
 | 
				
			||||||
 | 
					      return res.sendStatus(404)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const hasUpdated = req.user.addSeriesToHideFromContinueListening(req.params.id)
 | 
				
			||||||
 | 
					    if (hasUpdated) {
 | 
				
			||||||
 | 
					      await this.db.updateEntity('user', req.user)
 | 
				
			||||||
 | 
					      this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    res.json(req.user.toJSONForBrowser())
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
module.exports = new MeController()
 | 
					module.exports = new MeController()
 | 
				
			||||||
@ -5,7 +5,6 @@ class Series {
 | 
				
			|||||||
    this.id = null
 | 
					    this.id = null
 | 
				
			||||||
    this.name = null
 | 
					    this.name = null
 | 
				
			||||||
    this.description = null
 | 
					    this.description = null
 | 
				
			||||||
    this.hideFromHome = false
 | 
					 | 
				
			||||||
    this.addedAt = null
 | 
					    this.addedAt = null
 | 
				
			||||||
    this.updatedAt = null
 | 
					    this.updatedAt = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -18,7 +17,6 @@ class Series {
 | 
				
			|||||||
    this.id = series.id
 | 
					    this.id = series.id
 | 
				
			||||||
    this.name = series.name
 | 
					    this.name = series.name
 | 
				
			||||||
    this.description = series.description || null
 | 
					    this.description = series.description || null
 | 
				
			||||||
    this.hideFromHome = !!series.hideFromHome
 | 
					 | 
				
			||||||
    this.addedAt = series.addedAt
 | 
					    this.addedAt = series.addedAt
 | 
				
			||||||
    this.updatedAt = series.updatedAt
 | 
					    this.updatedAt = series.updatedAt
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -28,7 +26,6 @@ class Series {
 | 
				
			|||||||
      id: this.id,
 | 
					      id: this.id,
 | 
				
			||||||
      name: this.name,
 | 
					      name: this.name,
 | 
				
			||||||
      description: this.description,
 | 
					      description: this.description,
 | 
				
			||||||
      hideFromHome: this.hideFromHome,
 | 
					 | 
				
			||||||
      addedAt: this.addedAt,
 | 
					      addedAt: this.addedAt,
 | 
				
			||||||
      updatedAt: this.updatedAt
 | 
					      updatedAt: this.updatedAt
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -46,14 +43,13 @@ class Series {
 | 
				
			|||||||
    this.id = getId('ser')
 | 
					    this.id = getId('ser')
 | 
				
			||||||
    this.name = data.name
 | 
					    this.name = data.name
 | 
				
			||||||
    this.description = data.description || null
 | 
					    this.description = data.description || null
 | 
				
			||||||
    this.hideFromHome = !!data.hideFromHome
 | 
					 | 
				
			||||||
    this.addedAt = Date.now()
 | 
					    this.addedAt = Date.now()
 | 
				
			||||||
    this.updatedAt = Date.now()
 | 
					    this.updatedAt = Date.now()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  update(series) {
 | 
					  update(series) {
 | 
				
			||||||
    if (!series) return false
 | 
					    if (!series) return false
 | 
				
			||||||
    const keysToUpdate = ['name', 'description', 'hideFromHome']
 | 
					    const keysToUpdate = ['name', 'description']
 | 
				
			||||||
    var hasUpdated = false
 | 
					    var hasUpdated = false
 | 
				
			||||||
    for (const key of keysToUpdate) {
 | 
					    for (const key of keysToUpdate) {
 | 
				
			||||||
      if (series[key] !== undefined && series[key] !== this[key]) {
 | 
					      if (series[key] !== undefined && series[key] !== this[key]) {
 | 
				
			||||||
 | 
				
			|||||||
@ -15,6 +15,7 @@ class User {
 | 
				
			|||||||
    this.createdAt = null
 | 
					    this.createdAt = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.mediaProgress = []
 | 
					    this.mediaProgress = []
 | 
				
			||||||
 | 
					    this.seriesHideFromContinueListening = [] // Series IDs that should not show on home page continue listening
 | 
				
			||||||
    this.bookmarks = []
 | 
					    this.bookmarks = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.settings = {}
 | 
					    this.settings = {}
 | 
				
			||||||
@ -92,6 +93,7 @@ class User {
 | 
				
			|||||||
      type: this.type,
 | 
					      type: this.type,
 | 
				
			||||||
      token: this.token,
 | 
					      token: this.token,
 | 
				
			||||||
      mediaProgress: this.mediaProgress ? this.mediaProgress.map(li => li.toJSON()) : [],
 | 
					      mediaProgress: this.mediaProgress ? this.mediaProgress.map(li => li.toJSON()) : [],
 | 
				
			||||||
 | 
					      seriesHideFromContinueListening: [...this.seriesHideFromContinueListening],
 | 
				
			||||||
      bookmarks: this.bookmarks ? this.bookmarks.map(b => b.toJSON()) : [],
 | 
					      bookmarks: this.bookmarks ? this.bookmarks.map(b => b.toJSON()) : [],
 | 
				
			||||||
      isActive: this.isActive,
 | 
					      isActive: this.isActive,
 | 
				
			||||||
      isLocked: this.isLocked,
 | 
					      isLocked: this.isLocked,
 | 
				
			||||||
@ -111,6 +113,7 @@ class User {
 | 
				
			|||||||
      type: this.type,
 | 
					      type: this.type,
 | 
				
			||||||
      token: this.token,
 | 
					      token: this.token,
 | 
				
			||||||
      mediaProgress: this.mediaProgress ? this.mediaProgress.map(li => li.toJSON()) : [],
 | 
					      mediaProgress: this.mediaProgress ? this.mediaProgress.map(li => li.toJSON()) : [],
 | 
				
			||||||
 | 
					      seriesHideFromContinueListening: [...this.seriesHideFromContinueListening],
 | 
				
			||||||
      bookmarks: this.bookmarks ? this.bookmarks.map(b => b.toJSON()) : [],
 | 
					      bookmarks: this.bookmarks ? this.bookmarks.map(b => b.toJSON()) : [],
 | 
				
			||||||
      isActive: this.isActive,
 | 
					      isActive: this.isActive,
 | 
				
			||||||
      isLocked: this.isLocked,
 | 
					      isLocked: this.isLocked,
 | 
				
			||||||
@ -161,6 +164,9 @@ class User {
 | 
				
			|||||||
      this.bookmarks = user.bookmarks.filter(bm => typeof bm.libraryItemId == 'string').map(bm => new AudioBookmark(bm))
 | 
					      this.bookmarks = user.bookmarks.filter(bm => typeof bm.libraryItemId == 'string').map(bm => new AudioBookmark(bm))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.seriesHideFromContinueListening = []
 | 
				
			||||||
 | 
					    if (user.seriesHideFromContinueListening) this.seriesHideFromContinueListening = [...user.seriesHideFromContinueListening]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.isActive = (user.isActive === undefined || user.type === 'root') ? true : !!user.isActive
 | 
					    this.isActive = (user.isActive === undefined || user.type === 'root') ? true : !!user.isActive
 | 
				
			||||||
    this.isLocked = user.type === 'root' ? false : !!user.isLocked
 | 
					    this.isLocked = user.type === 'root' ? false : !!user.isLocked
 | 
				
			||||||
    this.lastSeen = user.lastSeen || null
 | 
					    this.lastSeen = user.lastSeen || null
 | 
				
			||||||
@ -196,6 +202,13 @@ class User {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (payload.seriesHideFromContinueListening && Array.isArray(payload.seriesHideFromContinueListening)) {
 | 
				
			||||||
 | 
					      if (this.seriesHideFromContinueListening.join(',') !== payload.seriesHideFromContinueListening.join(',')) {
 | 
				
			||||||
 | 
					        hasUpdates = true
 | 
				
			||||||
 | 
					        this.seriesHideFromContinueListening = [...payload.seriesHideFromContinueListening]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // And update permissions
 | 
					    // And update permissions
 | 
				
			||||||
    if (payload.permissions) {
 | 
					    if (payload.permissions) {
 | 
				
			||||||
      for (const key in payload.permissions) {
 | 
					      for (const key in payload.permissions) {
 | 
				
			||||||
@ -297,7 +310,13 @@ class User {
 | 
				
			|||||||
    return wasUpdated
 | 
					    return wasUpdated
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  removeMediaProgress(libraryItemId) {
 | 
					  removeMediaProgress(id) {
 | 
				
			||||||
 | 
					    if (!this.mediaProgress.some(mp => mp.id === id)) return false
 | 
				
			||||||
 | 
					    this.mediaProgress = this.mediaProgress.filter(mp => mp.id !== id)
 | 
				
			||||||
 | 
					    return true
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  removeMediaProgressForLibraryItem(libraryItemId) {
 | 
				
			||||||
    if (!this.mediaProgress.some(lip => lip.libraryItemId == libraryItemId)) return false
 | 
					    if (!this.mediaProgress.some(lip => lip.libraryItemId == libraryItemId)) return false
 | 
				
			||||||
    this.mediaProgress = this.mediaProgress.filter(lip => lip.libraryItemId != libraryItemId)
 | 
					    this.mediaProgress = this.mediaProgress.filter(lip => lip.libraryItemId != libraryItemId)
 | 
				
			||||||
    return true
 | 
					    return true
 | 
				
			||||||
@ -378,5 +397,15 @@ class User {
 | 
				
			|||||||
  removeBookmark(libraryItemId, time) {
 | 
					  removeBookmark(libraryItemId, time) {
 | 
				
			||||||
    this.bookmarks = this.bookmarks.filter(bm => (bm.libraryItemId !== libraryItemId || bm.time !== time))
 | 
					    this.bookmarks = this.bookmarks.filter(bm => (bm.libraryItemId !== libraryItemId || bm.time !== time))
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  checkShouldHideSeriesFromContinueListening(seriesId) {
 | 
				
			||||||
 | 
					    return this.seriesHideFromContinueListening.includes(seriesId)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  addSeriesToHideFromContinueListening(seriesId) {
 | 
				
			||||||
 | 
					    if (this.seriesHideFromContinueListening.includes(seriesId)) return false
 | 
				
			||||||
 | 
					    this.seriesHideFromContinueListening.push(seriesId)
 | 
				
			||||||
 | 
					    return true
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
module.exports = User
 | 
					module.exports = User
 | 
				
			||||||
@ -150,6 +150,7 @@ class ApiRouter {
 | 
				
			|||||||
    this.router.patch('/me/settings', MeController.updateSettings.bind(this))
 | 
					    this.router.patch('/me/settings', MeController.updateSettings.bind(this))
 | 
				
			||||||
    this.router.post('/me/sync-local-progress', MeController.syncLocalMediaProgress.bind(this))
 | 
					    this.router.post('/me/sync-local-progress', MeController.syncLocalMediaProgress.bind(this))
 | 
				
			||||||
    this.router.get('/me/items-in-progress', MeController.getAllLibraryItemsInProgress.bind(this))
 | 
					    this.router.get('/me/items-in-progress', MeController.getAllLibraryItemsInProgress.bind(this))
 | 
				
			||||||
 | 
					    this.router.post('/me/series/:id/hide', MeController.hideSeriesFromContinueListening.bind(this))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //
 | 
					    //
 | 
				
			||||||
    // Backup Routes
 | 
					    // Backup Routes
 | 
				
			||||||
@ -304,7 +305,7 @@ class ApiRouter {
 | 
				
			|||||||
    // Remove libraryItem from users
 | 
					    // Remove libraryItem from users
 | 
				
			||||||
    for (let i = 0; i < this.db.users.length; i++) {
 | 
					    for (let i = 0; i < this.db.users.length; i++) {
 | 
				
			||||||
      var user = this.db.users[i]
 | 
					      var user = this.db.users[i]
 | 
				
			||||||
      var madeUpdates = user.removeMediaProgress(libraryItem.id)
 | 
					      var madeUpdates = user.removeMediaProgressForLibraryItem(libraryItem.id)
 | 
				
			||||||
      if (madeUpdates) {
 | 
					      if (madeUpdates) {
 | 
				
			||||||
        await this.db.updateEntity('user', user)
 | 
					        await this.db.updateEntity('user', user)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
				
			|||||||
@ -415,13 +415,16 @@ module.exports = {
 | 
				
			|||||||
            const libraryItemJson = libraryItem.toJSONMinified()
 | 
					            const libraryItemJson = libraryItem.toJSONMinified()
 | 
				
			||||||
            libraryItemJson.seriesSequence = librarySeries.sequence
 | 
					            libraryItemJson.seriesSequence = librarySeries.sequence
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const hideFromContinueListening = user.checkShouldHideSeriesFromContinueListening(librarySeries.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!seriesMap[librarySeries.id]) {
 | 
					            if (!seriesMap[librarySeries.id]) {
 | 
				
			||||||
              const seriesObj = allSeries.find(se => se.id === librarySeries.id)
 | 
					              const seriesObj = allSeries.find(se => se.id === librarySeries.id)
 | 
				
			||||||
              if (seriesObj && !seriesObj.hideFromHome) {
 | 
					              if (seriesObj) {
 | 
				
			||||||
                var series = {
 | 
					                var series = {
 | 
				
			||||||
                  ...seriesObj.toJSON(),
 | 
					                  ...seriesObj.toJSON(),
 | 
				
			||||||
                  books: [libraryItemJson],
 | 
					                  books: [libraryItemJson],
 | 
				
			||||||
                  inProgress: bookInProgress,
 | 
					                  inProgress: bookInProgress,
 | 
				
			||||||
 | 
					                  hideFromContinueListening,
 | 
				
			||||||
                  bookInProgressLastUpdate: bookInProgress ? mediaProgress.lastUpdate : null,
 | 
					                  bookInProgressLastUpdate: bookInProgress ? mediaProgress.lastUpdate : null,
 | 
				
			||||||
                  firstBookUnread: bookInProgress ? null : libraryItemJson
 | 
					                  firstBookUnread: bookInProgress ? null : libraryItemJson
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@ -555,7 +558,7 @@ module.exports = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // For Continue Series - Find next book in series for series that are in progress
 | 
					    // For Continue Series - Find next book in series for series that are in progress
 | 
				
			||||||
    for (const seriesId in seriesMap) {
 | 
					    for (const seriesId in seriesMap) {
 | 
				
			||||||
      if (seriesMap[seriesId].inProgress) {
 | 
					      if (seriesMap[seriesId].inProgress && !seriesMap[seriesId].hideFromContinueListening) {
 | 
				
			||||||
        seriesMap[seriesId].books = naturalSort(seriesMap[seriesId].books).asc(li => li.seriesSequence)
 | 
					        seriesMap[seriesId].books = naturalSort(seriesMap[seriesId].books).asc(li => li.seriesSequence)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // NEW implementation takes the first book unread with the smallest series sequence
 | 
					        // NEW implementation takes the first book unread with the smallest series sequence
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user