diff --git a/client/components/app/StreamContainer.vue b/client/components/app/StreamContainer.vue
index 6398234f..4eaa0b6e 100644
--- a/client/components/app/StreamContainer.vue
+++ b/client/components/app/StreamContainer.vue
@@ -24,7 +24,6 @@
close
-
-
+
@@ -60,7 +59,6 @@ export default {
totalDuration: 0,
showBookmarksModal: false,
bookmarkCurrentTime: 0,
- bookmarkAudiobookId: null,
playerLoading: false,
isPlaying: false,
currentTime: 0,
@@ -103,9 +101,8 @@ export default {
return this.userLibraryItemProgress ? this.userLibraryItemProgress.currentTime || 0 : 0
},
bookmarks() {
- return []
- // if (!this.userAudiobook) return []
- // return (this.userAudiobook.bookmarks || []).map((bm) => ({ ...bm })).sort((a, b) => a.time - b.time)
+ if (!this.libraryItemId) return []
+ return this.$store.getters['user/getUserBookmarksForItem'](this.libraryItemId)
},
streamLibraryItem() {
return this.$store.state.streamLibraryItem
@@ -215,7 +212,6 @@ export default {
}
},
showBookmarks() {
- this.bookmarkAudiobookId = this.libraryItemId
this.bookmarkCurrentTime = this.currentTime
this.showBookmarksModal = true
},
diff --git a/client/components/modals/BookmarksModal.vue b/client/components/modals/BookmarksModal.vue
index 56035b88..273feb0c 100644
--- a/client/components/modals/BookmarksModal.vue
+++ b/client/components/modals/BookmarksModal.vue
@@ -39,7 +39,7 @@ export default {
type: Number,
default: 0
},
- audiobookId: String
+ libraryItemId: String
},
data() {
return {
@@ -76,8 +76,15 @@ export default {
this.showBookmarkTitleInput = true
},
deleteBookmark(bm) {
- var bookmark = { ...bm, audiobookId: this.audiobookId }
- this.$root.socket.emit('delete_bookmark', bookmark)
+ this.$axios
+ .$delete(`/api/me/item/${this.libraryItemId}/bookmark/${bm.time}`)
+ .then(() => {
+ this.$toast.success('Bookmark removed')
+ })
+ .catch((error) => {
+ this.$toast.error(`Failed to remove bookmark`)
+ console.error(error)
+ })
this.show = false
},
clickBookmark(bm) {
@@ -85,9 +92,15 @@ export default {
},
submitUpdateBookmark(updatedBookmark) {
var bookmark = { ...updatedBookmark }
- bookmark.audiobookId = this.audiobookId
-
- this.$root.socket.emit('update_bookmark', bookmark)
+ this.$axios
+ .$patch(`/api/me/item/${this.libraryItemId}/bookmark`, bookmark)
+ .then(() => {
+ this.$toast.success('Bookmark updated')
+ })
+ .catch((error) => {
+ this.$toast.error(`Failed to update bookmark`)
+ console.error(error)
+ })
this.show = false
},
submitCreateBookmark() {
@@ -95,11 +108,18 @@ export default {
this.newBookmarkTitle = this.$formatDate(Date.now(), 'MMM dd, yyyy HH:mm')
}
var bookmark = {
- audiobookId: this.audiobookId,
title: this.newBookmarkTitle,
- time: this.currentTime
+ time: Math.floor(this.currentTime)
}
- this.$root.socket.emit('create_bookmark', bookmark)
+ this.$axios
+ .$post(`/api/me/item/${this.libraryItemId}/bookmark`, bookmark)
+ .then(() => {
+ this.$toast.success('Bookmark added')
+ })
+ .catch((error) => {
+ this.$toast.error(`Failed to create bookmark`)
+ console.error(error)
+ })
this.newBookmarkTitle = ''
this.showBookmarkTitleInput = false
diff --git a/client/store/user.js b/client/store/user.js
index e8b9d700..da3826ae 100644
--- a/client/store/user.js
+++ b/client/store/user.js
@@ -26,6 +26,10 @@ export const getters = {
if (!state.user.libraryItemProgress) return null
return state.user.libraryItemProgress.find(li => li.id == libraryItemId)
},
+ getUserBookmarksForItem: (state) => (libraryItemId) => {
+ if (!state.user.bookmarks) return []
+ return state.user.bookmarks.filter(bm => bm.libraryItemId === libraryItemId)
+ },
getUserSetting: (state) => (key) => {
return state.settings ? state.settings[key] : null
},
@@ -97,7 +101,6 @@ export const actions = {
export const mutations = {
setUser(state, user) {
state.user = user
-
if (user) {
if (user.token) localStorage.setItem('token', user.token)
} else {
diff --git a/server/Server.js b/server/Server.js
index cf051b82..a51b512c 100644
--- a/server/Server.js
+++ b/server/Server.js
@@ -247,11 +247,6 @@ class Server {
socket.on('create_backup', () => this.backupManager.requestCreateBackup(socket))
socket.on('apply_backup', (id) => this.backupManager.requestApplyBackup(socket, id))
- // Bookmarks
- socket.on('create_bookmark', (payload) => this.createBookmark(socket, payload))
- socket.on('update_bookmark', (payload) => this.updateBookmark(socket, payload))
- socket.on('delete_bookmark', (payload) => this.deleteBookmark(socket, payload))
-
socket.on('disconnect', () => {
Logger.removeSocketListener(socket.id)
@@ -459,91 +454,6 @@ class Server {
res.sendStatus(200)
}
- // async audiobookProgressUpdate(socket, progressPayload) {
- // var client = socket.sheepClient
- // if (!client || !client.user) {
- // Logger.error('[Server] audiobookProgressUpdate invalid socket client')
- // return
- // }
- // var audiobookProgress = client.user.createUpdateLibraryItemProgress(progressPayload.audiobookId, progressPayload)
- // if (audiobookProgress) {
- // await this.db.updateEntity('user', client.user)
- // this.clientEmitter(client.user.id, 'current_user_audiobook_update', {
- // id: progressPayload.audiobookId,
- // data: audiobookProgress || null
- // })
- // }
- // }
-
- async createBookmark(socket, payload) {
- // var client = socket.sheepClient
- // if (!client || !client.user) {
- // Logger.error('[Server] createBookmark invalid socket client')
- // return
- // }
- // var userAudiobook = client.user.createBookmark(payload)
- // if (!userAudiobook || userAudiobook.error) {
- // var failMessage = (userAudiobook ? userAudiobook.error : null) || 'Unknown Error'
- // socket.emit('show_error_toast', `Failed to create Bookmark: ${failMessage}`)
- // return
- // }
-
- // await this.db.updateEntity('user', client.user)
-
- // socket.emit('show_success_toast', `${secondsToTimestamp(payload.time)} Bookmarked`)
-
- // this.clientEmitter(client.user.id, 'current_user_audiobook_update', {
- // id: userAudiobook.audiobookId,
- // data: userAudiobook || null
- // })
- }
-
- async updateBookmark(socket, payload) {
- // var client = socket.sheepClient
- // if (!client || !client.user) {
- // Logger.error('[Server] updateBookmark invalid socket client')
- // return
- // }
- // var userAudiobook = client.user.updateBookmark(payload)
- // if (!userAudiobook || userAudiobook.error) {
- // var failMessage = (userAudiobook ? userAudiobook.error : null) || 'Unknown Error'
- // socket.emit('show_error_toast', `Failed to update Bookmark: ${failMessage}`)
- // return
- // }
-
- // await this.db.updateEntity('user', client.user)
-
- // socket.emit('show_success_toast', `Bookmark ${secondsToTimestamp(payload.time)} Updated`)
-
- // this.clientEmitter(client.user.id, 'current_user_audiobook_update', {
- // id: userAudiobook.audiobookId,
- // data: userAudiobook || null
- // })
- }
-
- async deleteBookmark(socket, payload) {
- // var client = socket.sheepClient
- // if (!client || !client.user) {
- // Logger.error('[Server] deleteBookmark invalid socket client')
- // return
- // }
- // var userAudiobook = client.user.deleteBookmark(payload)
- // if (!userAudiobook || userAudiobook.error) {
- // var failMessage = (userAudiobook ? userAudiobook.error : null) || 'Unknown Error'
- // socket.emit('show_error_toast', `Failed to delete Bookmark: ${failMessage}`)
- // return
- // }
-
- // await this.db.updateEntity('user', client.user)
-
- // socket.emit('show_success_toast', `Bookmark ${secondsToTimestamp(payload.time)} Removed`)
-
- // this.clientEmitter(client.user.id, 'current_user_audiobook_update', {
- // id: userAudiobook.audiobookId,
- // data: userAudiobook || null
- // })
- }
-
async authenticateSocket(socket, token) {
var user = await this.auth.authenticateUser(token)
if (!user) {
diff --git a/server/controllers/MeController.js b/server/controllers/MeController.js
index e979375f..6adaaabe 100644
--- a/server/controllers/MeController.js
+++ b/server/controllers/MeController.js
@@ -69,6 +69,50 @@ class MeController {
res.sendStatus(200)
}
+ // POST: api/me/item/:id/bookmark
+ async createBookmark(req, res) {
+ var libraryItem = this.db.libraryItems.find(li => li.id === req.params.id)
+ if (!libraryItem) return res.sendStatus(404)
+ const { time, title } = req.body
+ var bookmark = req.user.createBookmark(libraryItem.id, time, title)
+ await this.db.updateEntity('user', req.user)
+ this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
+ res.json(bookmark)
+ }
+
+ // PATCH: api/me/item/:id/bookmark
+ async updateBookmark(req, res) {
+ var libraryItem = this.db.libraryItems.find(li => li.id === req.params.id)
+ if (!libraryItem) return res.sendStatus(404)
+ const { time, title } = req.body
+ if (!req.user.findBookmark(libraryItem.id, time)) {
+ Logger.error(`[MeController] updateBookmark not found`)
+ return res.sendStatus(404)
+ }
+ var bookmark = req.user.updateBookmark(libraryItem.id, time, title)
+ if (!bookmark) return res.sendStatus(500)
+ await this.db.updateEntity('user', req.user)
+ this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
+ res.json(bookmark)
+ }
+
+ // DELETE: api/me/item/:id/bookmark/:time
+ async removeBookmark(req, res) {
+ var libraryItem = this.db.libraryItems.find(li => li.id === req.params.id)
+ if (!libraryItem) return res.sendStatus(404)
+ var time = Number(req.params.time)
+ if (isNaN(time)) return res.sendStatus(500)
+
+ if (!req.user.findBookmark(libraryItem.id, time)) {
+ Logger.error(`[MeController] removeBookmark not found`)
+ return res.sendStatus(404)
+ }
+ req.user.removeBookmark(libraryItem.id, time)
+ await this.db.updateEntity('user', req.user)
+ this.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
+ res.sendStatus(200)
+ }
+
// PATCH: api/me/password
updatePassword(req, res) {
this.auth.userChangePassword(req, res)
diff --git a/server/objects/user/User.js b/server/objects/user/User.js
index 8d2b05fb..2cd40273 100644
--- a/server/objects/user/User.js
+++ b/server/objects/user/User.js
@@ -98,6 +98,7 @@ class User {
type: this.type,
token: this.token,
libraryItemProgress: this.libraryItemProgress ? this.libraryItemProgress.map(li => li.toJSON()) : [],
+ bookmarks: this.bookmarks ? this.bookmarks.map(b => b.toJSON()) : [],
isActive: this.isActive,
isLocked: this.isLocked,
lastSeen: this.lastSeen,
@@ -136,7 +137,7 @@ class User {
this.bookmarks = []
if (user.bookmarks) {
- this.bookmarks = user.bookmarks.map(bm => new AudioBookmark(bm))
+ this.bookmarks = user.bookmarks.filter(bm => typeof bm.libraryItemId == 'string').map(bm => new AudioBookmark(bm))
}
this.isActive = (user.isActive === undefined || user.type === 'root') ? true : !!user.isActive
@@ -260,54 +261,35 @@ class User {
return this.librariesAccessible.includes(libraryId)
}
- createBookmark({ libraryItemId, time, title }) {
- // if (!this.audiobooks) this.audiobooks = {}
- // if (!this.audiobooks[audiobookId]) {
- // this.audiobooks[audiobookId] = new UserAudiobookData()
- // this.audiobooks[audiobookId].audiobookId = audiobookId
- // }
- // if (this.audiobooks[audiobookId].checkBookmarkExists(time)) {
- // return {
- // error: 'Bookmark already exists'
- // }
- // }
-
- // var success = this.audiobooks[audiobookId].createBookmark(time, title)
- // if (success) return this.audiobooks[audiobookId]
- // return null
+ findBookmark(libraryItemId, time) {
+ return this.bookmarks.find(bm => bm.libraryItemId === libraryItemId && bm.time == time)
}
- updateBookmark({ audiobookId, time, title }) {
- // if (!this.audiobooks || !this.audiobooks[audiobookId]) {
- // return {
- // error: 'Invalid Audiobook'
- // }
- // }
- // if (!this.audiobooks[audiobookId].checkBookmarkExists(time)) {
- // return {
- // error: 'Bookmark does not exist'
- // }
- // }
-
- // var success = this.audiobooks[audiobookId].updateBookmark(time, title)
- // if (success) return this.audiobooks[audiobookId]
- // return null
+ createBookmark(libraryItemId, time, title) {
+ var existingBookmark = this.findBookmark(libraryItemId, time)
+ if (existingBookmark) {
+ Logger.warn('[User] Create Bookmark already exists for this time')
+ existingBookmark.title = title
+ return existingBookmark
+ }
+ var newBookmark = new AudioBookmark()
+ newBookmark.setData(libraryItemId, time, title)
+ this.bookmarks.push(newBookmark)
+ return newBookmark
}
- deleteBookmark({ audiobookId, time }) {
- // if (!this.audiobooks || !this.audiobooks[audiobookId]) {
- // return {
- // error: 'Invalid Audiobook'
- // }
- // }
- // if (!this.audiobooks[audiobookId].checkBookmarkExists(time)) {
- // return {
- // error: 'Bookmark does not exist'
- // }
- // }
+ updateBookmark(libraryItemId, time, title) {
+ var bookmark = this.findBookmark(libraryItemId, time)
+ if (!bookmark) {
+ Logger.error(`[User] updateBookmark not found`)
+ return null
+ }
+ bookmark.title = title
+ return bookmark
+ }
- // this.audiobooks[audiobookId].deleteBookmark(time)
- // return this.audiobooks[audiobookId]
+ removeBookmark(libraryItemId, time) {
+ this.bookmarks = this.bookmarks.filter(bm => (bm.libraryItemId !== libraryItemId || bm.time !== time))
}
syncLocalUserAudiobookData(localUserAudiobookData, audiobook) {
diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js
index 5c48bb79..db1d325d 100644
--- a/server/routers/ApiRouter.js
+++ b/server/routers/ApiRouter.js
@@ -134,6 +134,9 @@ class ApiRouter {
this.router.patch('/me/progress/:id', MeController.createUpdateLibraryItemProgress.bind(this))
this.router.delete('/me/progress/:id', MeController.removeLibraryItemProgress.bind(this))
this.router.patch('/me/progress/batch/update', MeController.batchUpdateLibraryItemProgress.bind(this))
+ this.router.post('/me/item/:id/bookmark', MeController.createBookmark.bind(this))
+ this.router.patch('/me/item/:id/bookmark', MeController.updateBookmark.bind(this))
+ this.router.delete('/me/item/:id/bookmark/:time', MeController.removeBookmark.bind(this))
this.router.patch('/me/password', MeController.updatePassword.bind(this))
this.router.patch('/me/settings', MeController.updateSettings.bind(this))
diff --git a/server/utils/dbMigration.js b/server/utils/dbMigration.js
index 549bb3b4..9134c638 100644
--- a/server/utils/dbMigration.js
+++ b/server/utils/dbMigration.js
@@ -262,9 +262,37 @@ async function migrateLibraryItems(db) {
lip.libraryItemId = libraryItemWithAudiobook.id
return lip
}).filter(lip => !!lip)
- await db.updateEntity('user', user)
- Logger.debug(`>>> User ${user.username} with ${user.libraryItemProgress.length} progress entries were updated`)
}
+ if (user.bookmarks.length) {
+ user.bookmarks = user.bookmarks.map((bookmark) => {
+ var audiobookId = bookmark.libraryItemId
+ var libraryItemWithAudiobook = libraryItems.find(li => li.media.getAudiobookById && !!li.media.getAudiobookById(audiobookId))
+ if (!libraryItemWithAudiobook) {
+ Logger.error('[dbMigration] Failed to find library item with audiobook id', audiobookId)
+ return null
+ }
+ bookmark.libraryItemId = libraryItemWithAudiobook.id
+ return bookmark
+ }).filter(bm => !!bm)
+ }
+ if (user.libraryItemProgress.length || user.bookmarks.length) {
+ await db.updateEntity('user', user)
+ }
+ }
+
+ // Update session LibraryItemId's
+ var sessions = await db.sessionsDb.select(() => true).then((results) => results.data)
+ if (sessions.length) {
+ sessions = sessions.map(se => {
+ var libraryItemWithAudiobook = libraryItems.find(li => li.media.getAudiobookById && !!li.media.getAudiobookById(se.mediaEntityId))
+ if (!libraryItemWithAudiobook) {
+ Logger.error('[dbMigration] Failed to find library item with audiobook id', audiobookId)
+ return null
+ }
+ se.libraryItemId = libraryItemWithAudiobook.id
+ return se
+ }).filter(se => !!se)
+ await db.updateEntities('session', sessions)
}
Logger.info(`>>> ${libraryItems.length} Library Items made`)
@@ -300,7 +328,7 @@ function cleanUserObject(db, userObj) {
// Bookmarks now live on User.js object instead of inside UserAudiobookData
if (userObj.audiobooks[audiobookId].bookmarks) {
const cleanedBookmarks = userObj.audiobooks[audiobookId].bookmarks.map((bm) => {
- bm.libraryItemId = audiobookId
+ bm.libraryItemId = audiobookId // Temp placeholder replace with libraryItemId when created
return bm
})
cleanedUserPayload.bookmarks = cleanedUserPayload.bookmarks.concat(cleanedBookmarks)
@@ -308,7 +336,7 @@ function cleanUserObject(db, userObj) {
var userAudiobookData = new UserAudiobookData(userObj.audiobooks[audiobookId]) // Legacy object
var liProgress = new LibraryItemProgress() // New Progress Object
- liProgress.id = userAudiobookData.audiobookId // This ID is INCORRECT, will be updated when library item is created
+ liProgress.id = userAudiobookData.audiobookId // This ID will be updated when library item is created
liProgress.libraryItemId = userAudiobookData.audiobookId
liProgress.isFinished = !!userAudiobookData.isRead
Object.keys(liProgress.toJSON()).forEach((key) => {
@@ -333,9 +361,10 @@ function cleanUserObject(db, userObj) {
function cleanSessionObj(db, userListeningSession) {
var newPlaybackSession = new PlaybackSession(userListeningSession)
+ newPlaybackSession.id = getId('play')
newPlaybackSession.mediaType = 'book'
newPlaybackSession.updatedAt = userListeningSession.lastUpdate
- newPlaybackSession.libraryItemId = userListeningSession.audiobookId
+ newPlaybackSession.libraryItemId = userListeningSession.audiobookId // Temp
newPlaybackSession.mediaEntityId = userListeningSession.audiobookId
newPlaybackSession.playMethod = PlayMethod.TRANSCODE