Add startup function to remove invalid records from DB

This commit is contained in:
advplyr 2023-09-05 17:58:13 -05:00
parent a44ee913c4
commit 10011bd6a3
5 changed files with 41 additions and 82 deletions

View File

@ -156,6 +156,7 @@ class Database {
await this.buildModels(force)
Logger.info(`[Database] Db initialized with models:`, Object.keys(this.sequelize.models).join(', '))
await this.cleanDatabase()
await this.loadData()
}
@ -380,27 +381,6 @@ class Database {
return this.models.libraryItem.fullUpdateFromOld(oldLibraryItem)
}
async updateBulkLibraryItems(oldLibraryItems) {
if (!this.sequelize) return false
let updatesMade = 0
for (const oldLibraryItem of oldLibraryItems) {
await oldLibraryItem.saveMetadata()
const hasUpdates = await this.models.libraryItem.fullUpdateFromOld(oldLibraryItem)
if (hasUpdates) {
updatesMade++
}
}
return updatesMade
}
async createBulkLibraryItems(oldLibraryItems) {
if (!this.sequelize) return false
for (const oldLibraryItem of oldLibraryItems) {
await oldLibraryItem.saveMetadata()
await this.models.libraryItem.fullCreateFromOld(oldLibraryItem)
}
}
async removeLibraryItem(libraryItemId) {
if (!this.sequelize) return false
await this.models.libraryItem.removeById(libraryItemId)
@ -675,6 +655,40 @@ class Database {
}
})
}
/**
* Clean invalid records in database
* Series should have atleast one Book
* Book and Podcast must have an associated LibraryItem
*/
async cleanDatabase() {
// Remove invalid Podcast records
const podcastsWithNoLibraryItem = await this.podcastModel.findAll({
where: Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM libraryItems li WHERE li.mediaId = podcast.id)`), 0)
})
for (const podcast of podcastsWithNoLibraryItem) {
Logger.warn(`Found podcast "${podcast.title}" with no libraryItem - removing it`)
await podcast.destroy()
}
// Remove invalid Book records
const booksWithNoLibraryItem = await this.bookModel.findAll({
where: Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM libraryItems li WHERE li.mediaId = book.id)`), 0)
})
for (const book of booksWithNoLibraryItem) {
Logger.warn(`Found book "${book.title}" with no libraryItem - removing it`)
await book.destroy()
}
// Remove empty series
const emptySeries = await this.seriesModel.findAll({
where: Sequelize.where(Sequelize.literal(`(SELECT count(*) FROM bookSeries bs WHERE bs.seriesId = series.id)`), 0)
})
for (const series of emptySeries) {
Logger.warn(`Found series "${series.name}" with no books - removing it`)
await series.destroy()
}
}
}
module.exports = new Database()

View File

@ -50,10 +50,14 @@ class BookSeries extends Model {
book.belongsToMany(series, { through: BookSeries })
series.belongsToMany(book, { through: BookSeries })
book.hasMany(BookSeries)
book.hasMany(BookSeries, {
onDelete: 'CASCADE'
})
BookSeries.belongsTo(book)
series.hasMany(BookSeries)
series.hasMany(BookSeries, {
onDelete: 'CASCADE'
})
BookSeries.belongsTo(series)
}
}

View File

@ -63,53 +63,6 @@ class LibraryItem extends Model {
this.updatedAt
}
/**
* Loads all podcast episodes, all library items in chunks of 500, then maps them to old library items
* @todo this is a temporary solution until we can use the sqlite without loading all the library items on init
*
* @returns {Promise<objects.LibraryItem[]>} old library items
*/
static async loadAllLibraryItems() {
let start = Date.now()
Logger.info(`[LibraryItem] Loading podcast episodes...`)
const podcastEpisodes = await this.sequelize.models.podcastEpisode.findAll()
Logger.info(`[LibraryItem] Finished loading ${podcastEpisodes.length} podcast episodes in ${((Date.now() - start) / 1000).toFixed(2)}s`)
start = Date.now()
Logger.info(`[LibraryItem] Loading library items...`)
let libraryItems = await this.getAllOldLibraryItemsIncremental()
Logger.info(`[LibraryItem] Finished loading ${libraryItems.length} library items in ${((Date.now() - start) / 1000).toFixed(2)}s`)
// Map LibraryItem to old library item
libraryItems = libraryItems.map(li => {
if (li.mediaType === 'podcast') {
li.media.podcastEpisodes = podcastEpisodes.filter(pe => pe.podcastId === li.media.id)
}
return this.getOldLibraryItem(li)
})
return libraryItems
}
/**
* Loads all LibraryItem in batches of 500
* @todo temporary solution
*
* @param {Model<LibraryItem>[]} libraryItems
* @param {number} offset
* @returns {Promise<Model<LibraryItem>[]>}
*/
static async getAllOldLibraryItemsIncremental(libraryItems = [], offset = 0) {
const limit = 500
const rows = await this.getLibraryItemsIncrement(offset, limit)
libraryItems.push(...rows)
if (!rows.length || rows.length < limit) {
return libraryItems
}
Logger.info(`[LibraryItem] Loaded ${rows.length} library items. ${libraryItems.length} loaded so far.`)
return this.getAllOldLibraryItemsIncremental(libraryItems, offset + rows.length)
}
/**
* Gets library items partially expanded, not including podcast episodes
* @todo temporary solution
@ -120,10 +73,6 @@ class LibraryItem extends Model {
*/
static getLibraryItemsIncrement(offset, limit, where = null) {
return this.findAll({
benchmark: true,
logging: (sql, timeMs) => {
console.log(`[Query] Elapsed ${timeMs}ms.`)
},
where,
include: [
{

View File

@ -547,10 +547,6 @@ module.exports = {
distinct: true,
attributes: bookAttributes,
replacements,
benchmark: true,
logging: (sql, timeMs) => {
console.log(`[Query] Elapsed ${timeMs}ms`)
},
include: [
{
model: Database.libraryItemModel,

View File

@ -141,10 +141,6 @@ module.exports = {
offset,
distinct: true,
subQuery: false,
benchmark: true,
logging: (sql, timeMs) => {
console.log(`[Query] Series filter/sort. Elapsed ${timeMs}ms`)
},
attributes: seriesAttributes,
replacements: userPermissionBookWhere.replacements,
include: [