mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Update get User API endpoint to load media progress from db
This commit is contained in:
		
							parent
							
								
									1ebe8a6f4c
								
							
						
					
					
						commit
						361732a463
					
				| @ -13,8 +13,8 @@ | ||||
| 
 | ||||
|     <div v-if="imageFailed" class="absolute top-0 left-0 right-0 bottom-0 w-full h-full bg-red-100" :style="{ padding: placeholderCoverPadding + 'rem' }"> | ||||
|       <div class="w-full h-full border-2 border-error flex flex-col items-center justify-center"> | ||||
|         <img src="/Logo.png" class="mb-2" :style="{ height: 64 * sizeMultiplier + 'px' }" /> | ||||
|         <p class="text-center text-error" :style="{ fontSize: sizeMultiplier + 'rem' }">Invalid Cover</p> | ||||
|         <img v-if="width > 100" src="/Logo.png" class="mb-2" :style="{ height: 40 * sizeMultiplier + 'px' }" /> | ||||
|         <p class="text-center text-error" :style="{ fontSize: invalidCoverFontSize + 'rem' }">Invalid Cover</p> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
| @ -58,6 +58,9 @@ export default { | ||||
|     sizeMultiplier() { | ||||
|       return this.width / 120 | ||||
|     }, | ||||
|     invalidCoverFontSize() { | ||||
|       return Math.max(this.sizeMultiplier * 0.8, 0.5) | ||||
|     }, | ||||
|     placeholderCoverPadding() { | ||||
|       return 0.8 * this.sizeMultiplier | ||||
|     }, | ||||
|  | ||||
| @ -47,7 +47,7 @@ | ||||
|       <div class="py-2"> | ||||
|         <h1 class="text-lg mb-2 text-white text-opacity-90 px-2 sm:px-0">{{ $strings.HeaderSavedMediaProgress }}</h1> | ||||
| 
 | ||||
|         <table v-if="mediaProgressWithMedia.length" class="userAudiobooksTable"> | ||||
|         <table v-if="mediaProgress.length" class="userAudiobooksTable"> | ||||
|           <tr class="bg-primary bg-opacity-40"> | ||||
|             <th class="w-16 text-left">{{ $strings.LabelItem }}</th> | ||||
|             <th class="text-left"></th> | ||||
| @ -55,19 +55,14 @@ | ||||
|             <th class="w-40 hidden sm:table-cell">{{ $strings.LabelStartedAt }}</th> | ||||
|             <th class="w-40 hidden sm:table-cell">{{ $strings.LabelLastUpdate }}</th> | ||||
|           </tr> | ||||
|           <tr v-for="item in mediaProgressWithMedia" :key="item.id" :class="!item.isFinished ? '' : 'isFinished'"> | ||||
|           <tr v-for="item in mediaProgress" :key="item.id" :class="!item.isFinished ? '' : 'isFinished'"> | ||||
|             <td> | ||||
|               <covers-book-cover :width="50" :library-item="item" :book-cover-aspect-ratio="bookCoverAspectRatio" /> | ||||
|               <covers-preview-cover v-if="item.coverPath" :width="50" :src="$store.getters['globals/getLibraryItemCoverSrcById'](item.libraryItemId, item.mediaUpdatedAt)" :book-cover-aspect-ratio="bookCoverAspectRatio" :show-resolution="false" /> | ||||
|               <div v-else class="bg-primary flex items-center justify-center text-center text-xs text-gray-400 p-1" :style="{ width: '50px', height: 50 * bookCoverAspectRatio + 'px' }">No Cover</div> | ||||
|             </td> | ||||
|             <td> | ||||
|               <template v-if="item.media && item.media.metadata && item.episode"> | ||||
|                 <p>{{ item.episode.title || 'Unknown' }}</p> | ||||
|                 <p class="text-white text-opacity-50 text-sm font-sans">{{ item.media.metadata.title }}</p> | ||||
|               </template> | ||||
|               <template v-else-if="item.media && item.media.metadata"> | ||||
|                 <p>{{ item.media.metadata.title || 'Unknown' }}</p> | ||||
|                 <p v-if="item.media.metadata.authorName" class="text-white text-opacity-50 text-sm font-sans">by {{ item.media.metadata.authorName }}</p> | ||||
|               </template> | ||||
|               <p>{{ item.displayTitle || 'Unknown' }}</p> | ||||
|               <p v-if="item.displaySubtitle" class="text-white text-opacity-50 text-sm font-sans">{{ item.displaySubtitle }}</p> | ||||
|             </td> | ||||
|             <td class="text-center"> | ||||
|               <p class="text-sm">{{ Math.floor(item.progress * 100) }}%</p> | ||||
| @ -124,9 +119,6 @@ export default { | ||||
|     mediaProgress() { | ||||
|       return this.user.mediaProgress.sort((a, b) => b.lastUpdate - a.lastUpdate) | ||||
|     }, | ||||
|     mediaProgressWithMedia() { | ||||
|       return this.mediaProgress.filter((mp) => mp.media) | ||||
|     }, | ||||
|     totalListeningTime() { | ||||
|       return this.listeningStats.totalTime || 0 | ||||
|     }, | ||||
|  | ||||
| @ -32,13 +32,60 @@ class UserController { | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * GET: /api/users/:id | ||||
|    * Get a single user toJSONForBrowser | ||||
|    * Media progress items include: `displayTitle`, `displaySubtitle` (for podcasts), `coverPath` and `mediaUpdatedAt` | ||||
|    *  | ||||
|    * @param {import("express").Request} req  | ||||
|    * @param {import("express").Response} res  | ||||
|    */ | ||||
|   async findOne(req, res) { | ||||
|     if (!req.user.isAdminOrUp) { | ||||
|       Logger.error('User other than admin attempting to get user', req.user) | ||||
|       return res.sendStatus(403) | ||||
|     } | ||||
| 
 | ||||
|     res.json(this.userJsonWithItemProgressDetails(req.reqUser, !req.user.isRoot)) | ||||
|     // Get user media progress with associated mediaItem
 | ||||
|     const mediaProgresses = await Database.models.mediaProgress.findAll({ | ||||
|       where: { | ||||
|         userId: req.reqUser.id | ||||
|       }, | ||||
|       include: [ | ||||
|         { | ||||
|           model: Database.models.book, | ||||
|           attributes: ['id', 'title', 'coverPath', 'updatedAt'] | ||||
|         }, | ||||
|         { | ||||
|           model: Database.models.podcastEpisode, | ||||
|           attributes: ['id', 'title'], | ||||
|           include: { | ||||
|             model: Database.models.podcast, | ||||
|             attributes: ['id', 'title', 'coverPath', 'updatedAt'] | ||||
|           } | ||||
|         } | ||||
|       ] | ||||
|     }) | ||||
| 
 | ||||
|     const oldMediaProgresses = mediaProgresses.map(mp => { | ||||
|       const oldMediaProgress = mp.getOldMediaProgress() | ||||
|       oldMediaProgress.displayTitle = mp.mediaItem?.title | ||||
|       if (mp.mediaItem?.podcast) { | ||||
|         oldMediaProgress.displaySubtitle = mp.mediaItem.podcast?.title | ||||
|         oldMediaProgress.coverPath = mp.mediaItem.podcast?.coverPath | ||||
|         oldMediaProgress.mediaUpdatedAt = mp.mediaItem.podcast?.updatedAt | ||||
|       } else if (mp.mediaItem) { | ||||
|         oldMediaProgress.coverPath = mp.mediaItem.coverPath | ||||
|         oldMediaProgress.mediaUpdatedAt = mp.mediaItem.updatedAt | ||||
|       } | ||||
|       return oldMediaProgress | ||||
|     }) | ||||
| 
 | ||||
|     const userJson = req.reqUser.toJSONForBrowser(!req.user.isRoot) | ||||
| 
 | ||||
|     userJson.mediaProgress = oldMediaProgresses | ||||
| 
 | ||||
|     res.json(userJson) | ||||
|   } | ||||
| 
 | ||||
|   async create(req, res) { | ||||
|  | ||||
| @ -352,34 +352,6 @@ class ApiRouter { | ||||
|   //
 | ||||
|   // Helper Methods
 | ||||
|   //
 | ||||
|   userJsonWithItemProgressDetails(user, hideRootToken = false) { | ||||
|     const json = user.toJSONForBrowser(hideRootToken) | ||||
| 
 | ||||
|     json.mediaProgress = json.mediaProgress.map(lip => { | ||||
|       const libraryItem = Database.libraryItems.find(li => li.id === lip.libraryItemId) | ||||
|       if (!libraryItem) { | ||||
|         Logger.warn('[ApiRouter] Library item not found for users progress ' + lip.libraryItemId) | ||||
|         lip.media = null | ||||
|       } else { | ||||
|         if (lip.episodeId) { | ||||
|           const episode = libraryItem.mediaType === 'podcast' ? libraryItem.media.getEpisode(lip.episodeId) : null | ||||
|           if (!episode) { | ||||
|             Logger.warn(`[ApiRouter] Episode ${lip.episodeId} not found for user media progress, podcast: ${libraryItem.media.metadata.title}`) | ||||
|             lip.media = null | ||||
|           } else { | ||||
|             lip.media = libraryItem.media.toJSONExpanded() | ||||
|             lip.episode = episode.toJSON() | ||||
|           } | ||||
|         } else { | ||||
|           lip.media = libraryItem.media.toJSONExpanded() | ||||
|         } | ||||
|       } | ||||
|       return lip | ||||
|     }).filter(lip => !!lip) | ||||
| 
 | ||||
|     return json | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Remove library item and associated entities | ||||
|    * @param {string} mediaType  | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user