Update filterData for authors/series when added/removed

This commit is contained in:
advplyr 2023-08-18 14:40:36 -05:00
parent 7222171c5b
commit 9d7d4c6902
12 changed files with 152 additions and 13 deletions

View File

@ -343,6 +343,10 @@ export default {
} }
this.$store.commit('libraries/removeCollection', collection) this.$store.commit('libraries/removeCollection', collection)
}, },
seriesRemoved({ id, libraryId }) {
if (this.currentLibraryId !== libraryId) return
this.$store.commit('libraries/removeSeriesFromFilterData', id)
},
playlistAdded(playlist) { playlistAdded(playlist) {
if (playlist.userId !== this.user.id || this.currentLibraryId !== playlist.libraryId) return if (playlist.userId !== this.user.id || this.currentLibraryId !== playlist.libraryId) return
this.$store.commit('libraries/addUpdateUserPlaylist', playlist) this.$store.commit('libraries/addUpdateUserPlaylist', playlist)
@ -442,6 +446,9 @@ export default {
this.socket.on('collection_updated', this.collectionUpdated) this.socket.on('collection_updated', this.collectionUpdated)
this.socket.on('collection_removed', this.collectionRemoved) this.socket.on('collection_removed', this.collectionRemoved)
// Series Listeners
this.socket.on('series_removed', this.seriesRemoved)
// User Playlist Listeners // User Playlist Listeners
this.socket.on('playlist_added', this.playlistAdded) this.socket.on('playlist_added', this.playlistAdded)
this.socket.on('playlist_updated', this.playlistUpdated) this.socket.on('playlist_updated', this.playlistUpdated)

View File

@ -234,6 +234,10 @@ export const mutations = {
setNumUserPlaylists(state, numUserPlaylists) { setNumUserPlaylists(state, numUserPlaylists) {
state.numUserPlaylists = numUserPlaylists state.numUserPlaylists = numUserPlaylists
}, },
removeSeriesFromFilterData(state, seriesId) {
if (!seriesId || !state.filterData) return
state.filterData.series = state.filterData.series.filter(se => se.id !== seriesId)
},
updateFilterDataWithItem(state, libraryItem) { updateFilterDataWithItem(state, libraryItem) {
if (!libraryItem || !state.filterData) return if (!libraryItem || !state.filterData) return
if (state.currentLibraryId !== libraryItem.libraryId) return if (state.currentLibraryId !== libraryItem.libraryId) return

View File

@ -34,6 +34,16 @@ class Database {
return this.sequelize?.models || {} return this.sequelize?.models || {}
} }
/** @type {typeof import('./models/Author')} */
get authorModel() {
return this.models.author
}
/** @type {typeof import('./models/Series')} */
get seriesModel() {
return this.models.series
}
async checkHasDb() { async checkHasDb() {
if (!await fs.pathExists(this.dbPath)) { if (!await fs.pathExists(this.dbPath)) {
Logger.info(`[Database] absdatabase.sqlite not found at ${this.dbPath}`) Logger.info(`[Database] absdatabase.sqlite not found at ${this.dbPath}`)
@ -481,6 +491,66 @@ class Database {
} }
} }
} }
removeSeriesFromFilterData(libraryId, seriesId) {
if (!this.libraryFilterData[libraryId]) return
this.libraryFilterData[libraryId].series = this.libraryFilterData[libraryId].series.filter(se => se.id !== seriesId)
}
addSeriesToFilterData(libraryId, seriesName, seriesId) {
if (!this.libraryFilterData[libraryId]) return
// Check if series is already added
if (this.libraryFilterData[libraryId].series.some(se => se.id === seriesId)) return
this.libraryFilterData[libraryId].series.push({
id: seriesId,
name: seriesName
})
}
removeAuthorFromFilterData(libraryId, authorId) {
if (!this.libraryFilterData[libraryId]) return
this.libraryFilterData[libraryId].authors = this.libraryFilterData[libraryId].authors.filter(au => au.id !== authorId)
}
addAuthorToFilterData(libraryId, authorName, authorId) {
if (!this.libraryFilterData[libraryId]) return
// Check if author is already added
if (this.libraryFilterData[libraryId].authors.some(au => au.id === authorId)) return
this.libraryFilterData[libraryId].authors.push({
id: authorId,
name: authorName
})
}
/**
* Used when updating items to make sure author id exists
* If library filter data is set then use that for check
* otherwise lookup in db
* @param {string} libraryId
* @param {string} authorId
* @returns {Promise<boolean>}
*/
async checkAuthorExists(libraryId, authorId) {
if (!this.libraryFilterData[libraryId]) {
return this.authorModel.checkExistsById(authorId)
}
return this.libraryFilterData[libraryId].authors.some(au => au.id === authorId)
}
/**
* Used when updating items to make sure series id exists
* If library filter data is set then use that for check
* otherwise lookup in db
* @param {string} libraryId
* @param {string} seriesId
* @returns {Promise<boolean>}
*/
async checkSeriesExists(libraryId, seriesId) {
if (!this.libraryFilterData[libraryId]) {
return this.seriesModel.checkExistsById(seriesId)
}
return this.libraryFilterData[libraryId].series.some(se => se.id === seriesId)
}
} }
module.exports = new Database() module.exports = new Database()

View File

@ -113,6 +113,8 @@ class AuthorController {
// Remove old author // Remove old author
await Database.removeAuthor(req.author.id) await Database.removeAuthor(req.author.id)
SocketAuthority.emitter('author_removed', req.author.toJSON()) SocketAuthority.emitter('author_removed', req.author.toJSON())
// Update filter data
Database.removeAuthorFromFilterData(req.author.libraryId, req.author.id)
// Send updated num books for merged author // Send updated num books for merged author
const numBooks = await Database.models.libraryItem.getForAuthor(existingAuthor).length const numBooks = await Database.models.libraryItem.getForAuthor(existingAuthor).length

View File

@ -859,12 +859,12 @@ class LibraryController {
/** /**
* GET: /api/libraries/:id/authors * GET: /api/libraries/:id/authors
* Get authors for library * Get authors for library
* @param {*} req * @param {import('express').Request} req
* @param {*} res * @param {import('express').Response} res
*/ */
async getAuthors(req, res) { async getAuthors(req, res) {
const { bookWhere, replacements } = libraryItemsBookFilters.getUserPermissionBookWhereQuery(req.user) const { bookWhere, replacements } = libraryItemsBookFilters.getUserPermissionBookWhereQuery(req.user)
const authors = await Database.models.author.findAll({ const authors = await Database.authorModel.findAll({
where: { where: {
libraryId: req.library.id libraryId: req.library.id
}, },

View File

@ -124,7 +124,7 @@ class LibraryItemController {
// Book specific - Get all series being removed from this item // Book specific - Get all series being removed from this item
let seriesRemoved = [] let seriesRemoved = []
if (libraryItem.isBook && mediaPayload.metadata?.series) { if (libraryItem.isBook && mediaPayload.metadata?.series) {
const seriesIdsInUpdate = (mediaPayload.metadata?.series || []).map(se => se.id) const seriesIdsInUpdate = mediaPayload.metadata.series?.map(se => se.id) || []
seriesRemoved = libraryItem.media.metadata.series.filter(se => !seriesIdsInUpdate.includes(se.id)) seriesRemoved = libraryItem.media.metadata.series.filter(se => !seriesIdsInUpdate.includes(se.id))
} }

View File

@ -83,6 +83,15 @@ class Author extends Model {
}) })
} }
/**
* Check if author exists
* @param {string} authorId
* @returns {Promise<boolean>}
*/
static async checkExistsById(authorId) {
return (await this.count({ where: { id: authorId } })) > 0
}
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize

View File

@ -75,6 +75,15 @@ class Series extends Model {
}) })
} }
/**
* Check if series exists
* @param {string} seriesId
* @returns {Promise<boolean>}
*/
static async checkExistsById(seriesId) {
return (await this.count({ where: { id: seriesId } })) > 0
}
/** /**
* Initialize model * Initialize model
* @param {import('../Database').sequelize} sequelize * @param {import('../Database').sequelize} sequelize

View File

@ -472,10 +472,20 @@ class ApiRouter {
} }
} }
/**
* Remove an empty series & close an open RSS feed
* @param {import('../models/Series')} series
*/
async removeEmptySeries(series) { async removeEmptySeries(series) {
await this.rssFeedManager.closeFeedForEntityId(series.id) await this.rssFeedManager.closeFeedForEntityId(series.id)
Logger.info(`[ApiRouter] Series "${series.name}" is now empty. Removing series`) Logger.info(`[ApiRouter] Series "${series.name}" is now empty. Removing series`)
await Database.removeSeries(series.id) await Database.removeSeries(series.id)
// Remove series from library filter data
Database.removeSeriesFromFilterData(series.libraryId, series.id)
SocketAuthority.emitter('series_removed', {
id: series.id,
libraryId: series.libraryId
})
} }
async getUserListeningSessionsHelper(userId) { async getUserListeningSessionsHelper(userId) {
@ -546,7 +556,7 @@ class ApiRouter {
const mediaMetadata = mediaPayload.metadata const mediaMetadata = mediaPayload.metadata
// Create new authors if in payload // Create new authors if in payload
if (mediaMetadata.authors && mediaMetadata.authors.length) { if (mediaMetadata.authors?.length) {
const newAuthors = [] const newAuthors = []
for (let i = 0; i < mediaMetadata.authors.length; i++) { for (let i = 0; i < mediaMetadata.authors.length; i++) {
const authorName = (mediaMetadata.authors[i].name || '').trim() const authorName = (mediaMetadata.authors[i].name || '').trim()
@ -555,6 +565,12 @@ class ApiRouter {
continue continue
} }
// Ensure the ID for the author exists
if (mediaMetadata.authors[i].id && !(await Database.checkAuthorExists(libraryId, mediaMetadata.authors[i].id))) {
Logger.warn(`[ApiRouter] Author id "${mediaMetadata.authors[i].id}" does not exist`)
mediaMetadata.authors[i].id = null
}
if (!mediaMetadata.authors[i].id || mediaMetadata.authors[i].id.startsWith('new')) { if (!mediaMetadata.authors[i].id || mediaMetadata.authors[i].id.startsWith('new')) {
let author = Database.authors.find(au => au.libraryId === libraryId && au.checkNameEquals(authorName)) let author = Database.authors.find(au => au.libraryId === libraryId && au.checkNameEquals(authorName))
if (!author) { if (!author) {
@ -562,6 +578,8 @@ class ApiRouter {
author.setData(mediaMetadata.authors[i], libraryId) author.setData(mediaMetadata.authors[i], libraryId)
Logger.debug(`[ApiRouter] Created new author "${author.name}"`) Logger.debug(`[ApiRouter] Created new author "${author.name}"`)
newAuthors.push(author) newAuthors.push(author)
// Update filter data
Database.addAuthorToFilterData(libraryId, author.name, author.id)
} }
// Update ID in original payload // Update ID in original payload
@ -584,6 +602,12 @@ class ApiRouter {
continue continue
} }
// Ensure the ID for the series exists
if (mediaMetadata.series[i].id && !(await Database.checkSeriesExists(libraryId, mediaMetadata.series[i].id))) {
Logger.warn(`[ApiRouter] Series id "${mediaMetadata.series[i].id}" does not exist`)
mediaMetadata.series[i].id = null
}
if (!mediaMetadata.series[i].id || mediaMetadata.series[i].id.startsWith('new')) { if (!mediaMetadata.series[i].id || mediaMetadata.series[i].id.startsWith('new')) {
let seriesItem = Database.series.find(se => se.libraryId === libraryId && se.checkNameEquals(seriesName)) let seriesItem = Database.series.find(se => se.libraryId === libraryId && se.checkNameEquals(seriesName))
if (!seriesItem) { if (!seriesItem) {
@ -591,6 +615,8 @@ class ApiRouter {
seriesItem.setData(mediaMetadata.series[i], libraryId) seriesItem.setData(mediaMetadata.series[i], libraryId)
Logger.debug(`[ApiRouter] Created new series "${seriesItem.name}"`) Logger.debug(`[ApiRouter] Created new series "${seriesItem.name}"`)
newSeries.push(seriesItem) newSeries.push(seriesItem)
// Update filter data
Database.addSeriesToFilterData(libraryId, seriesItem.name, seriesItem.id)
} }
// Update ID in original payload // Update ID in original payload

View File

@ -486,6 +486,8 @@ class Scanner {
_author = new Author() _author = new Author()
_author.setData(tempMinAuthor, libraryItem.libraryId) _author.setData(tempMinAuthor, libraryItem.libraryId)
newAuthors.push(_author) newAuthors.push(_author)
// Update filter data
Database.addAuthorToFilterData(libraryItem.libraryId, _author.name, _author.id)
} }
return { return {
@ -502,11 +504,17 @@ class Scanner {
const newSeries = [] const newSeries = []
libraryItem.media.metadata.series = libraryItem.media.metadata.series.map((tempMinSeries) => { libraryItem.media.metadata.series = libraryItem.media.metadata.series.map((tempMinSeries) => {
let _series = Database.series.find(se => se.libraryId === libraryItem.libraryId && se.checkNameEquals(tempMinSeries.name)) let _series = Database.series.find(se => se.libraryId === libraryItem.libraryId && se.checkNameEquals(tempMinSeries.name))
if (!_series) _series = newSeries.find(se => se.libraryId === libraryItem.libraryId && se.checkNameEquals(tempMinSeries.name)) // Check new unsaved series if (!_series) {
// Check new unsaved series
_series = newSeries.find(se => se.libraryId === libraryItem.libraryId && se.checkNameEquals(tempMinSeries.name))
}
if (!_series) { // Must create new series if (!_series) { // Must create new series
_series = new Series() _series = new Series()
_series.setData(tempMinSeries, libraryItem.libraryId) _series.setData(tempMinSeries, libraryItem.libraryId)
newSeries.push(_series) newSeries.push(_series)
// Update filter data
Database.addSeriesToFilterData(libraryItem.libraryId, _series.name, _series.id)
} }
return { return {
id: _series.id, id: _series.id,
@ -924,6 +932,8 @@ class Scanner {
author.setData({ name: authorName }, libraryItem.libraryId) author.setData({ name: authorName }, libraryItem.libraryId)
await Database.createAuthor(author) await Database.createAuthor(author)
SocketAuthority.emitter('author_added', author.toJSON()) SocketAuthority.emitter('author_added', author.toJSON())
// Update filter data
Database.addAuthorToFilterData(libraryItem.libraryId, author.name, author.id)
} }
authorPayload.push(author.toJSONMinimal()) authorPayload.push(author.toJSONMinimal())
} }
@ -940,6 +950,8 @@ class Scanner {
seriesItem = new Series() seriesItem = new Series()
seriesItem.setData({ name: seriesMatchItem.series }, libraryItem.libraryId) seriesItem.setData({ name: seriesMatchItem.series }, libraryItem.libraryId)
await Database.createSeries(seriesItem) await Database.createSeries(seriesItem)
// Update filter data
Database.addSeriesToFilterData(libraryItem.libraryId, seriesItem.name, seriesItem.id)
SocketAuthority.emitter('series_added', seriesItem.toJSON()) SocketAuthority.emitter('series_added', seriesItem.toJSON())
} }
seriesPayload.push(seriesItem.toJSONMinimal(seriesMatchItem.sequence)) seriesPayload.push(seriesItem.toJSONMinimal(seriesMatchItem.sequence))

View File

@ -221,7 +221,7 @@ module.exports = {
})) }))
} }
const { rows: series, count } = await Database.models.series.findAndCountAll({ const { rows: series, count } = await Database.seriesModel.findAndCountAll({
where: seriesWhere, where: seriesWhere,
limit, limit,
offset: 0, offset: 0,
@ -291,7 +291,7 @@ module.exports = {
async getNewestAuthors(library, user, 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.authorModel.findAndCountAll({
where: { where: {
libraryId: library.id, libraryId: library.id,
createdAt: { createdAt: {
@ -461,7 +461,7 @@ module.exports = {
if (book.language) data.languages.add(book.language) if (book.language) data.languages.add(book.language)
} }
const series = await Database.models.series.findAll({ const series = await Database.seriesModel.findAll({
where: { where: {
libraryId: oldLibrary.id libraryId: oldLibrary.id
}, },
@ -469,7 +469,7 @@ module.exports = {
}) })
series.forEach((s) => data.series.push({ id: s.id, name: s.name })) series.forEach((s) => data.series.push({ id: s.id, name: s.name }))
const authors = await Database.models.author.findAll({ const authors = await Database.authorModel.findAll({
where: { where: {
libraryId: oldLibrary.id libraryId: oldLibrary.id
}, },

View File

@ -278,7 +278,7 @@ module.exports = {
* @returns {object} { booksToExclude, bookSeriesToInclude } * @returns {object} { booksToExclude, bookSeriesToInclude }
*/ */
async getCollapseSeriesBooksToExclude(bookFindOptions, seriesWhere) { async getCollapseSeriesBooksToExclude(bookFindOptions, seriesWhere) {
const allSeries = await Database.models.series.findAll({ const allSeries = await Database.seriesModel.findAll({
attributes: [ attributes: [
'id', 'id',
'name', 'name',
@ -642,7 +642,7 @@ module.exports = {
const userPermissionBookWhere = this.getUserPermissionBookWhereQuery(user) const userPermissionBookWhere = this.getUserPermissionBookWhereQuery(user)
bookWhere.push(...userPermissionBookWhere.bookWhere) bookWhere.push(...userPermissionBookWhere.bookWhere)
const { rows: series, count } = await Database.models.series.findAndCountAll({ const { rows: series, count } = await Database.seriesModel.findAndCountAll({
where: [ where: [
{ {
libraryId libraryId
@ -751,7 +751,7 @@ module.exports = {
const userPermissionBookWhere = this.getUserPermissionBookWhereQuery(user) 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.seriesModel.findAll({
where: [ where: [
{ {
libraryId libraryId