Fix FeedEpisodes using a new ID when updating #3757

This commit is contained in:
advplyr 2025-01-01 11:32:39 -06:00
parent 8c4d0c503b
commit 5201625d38
3 changed files with 71 additions and 24 deletions

View File

@ -98,11 +98,22 @@ class RssFeedManager {
podcastId: feed.entity.mediaId podcastId: feed.entity.mediaId
}, },
attributes: ['id', 'updatedAt'], attributes: ['id', 'updatedAt'],
order: [['createdAt', 'DESC']] order: [['updatedAt', 'DESC']]
}) })
if (mostRecentPodcastEpisode && mostRecentPodcastEpisode.updatedAt > newEntityUpdatedAt) { if (mostRecentPodcastEpisode && mostRecentPodcastEpisode.updatedAt > newEntityUpdatedAt) {
newEntityUpdatedAt = mostRecentPodcastEpisode.updatedAt newEntityUpdatedAt = mostRecentPodcastEpisode.updatedAt
} }
} else {
const book = await Database.bookModel.findOne({
where: {
id: feed.entity.mediaId
},
attributes: ['id', 'updatedAt']
})
if (book && book.updatedAt > newEntityUpdatedAt) {
newEntityUpdatedAt = book.updatedAt
}
} }
return newEntityUpdatedAt > feed.entityUpdatedAt return newEntityUpdatedAt > feed.entityUpdatedAt
@ -111,7 +122,7 @@ class RssFeedManager {
attributes: ['id', 'updatedAt'], attributes: ['id', 'updatedAt'],
include: { include: {
model: Database.bookModel, model: Database.bookModel,
attributes: ['id'], attributes: ['id', 'updatedAt'],
through: { through: {
attributes: [] attributes: []
}, },
@ -125,8 +136,9 @@ class RssFeedManager {
let newEntityUpdatedAt = feed.entity.updatedAt let newEntityUpdatedAt = feed.entity.updatedAt
const mostRecentItemUpdatedAt = feed.entity.books.reduce((mostRecent, book) => { const mostRecentItemUpdatedAt = feed.entity.books.reduce((mostRecent, book) => {
if (book.libraryItem.updatedAt > mostRecent) { let updatedAt = book.libraryItem.updatedAt > book.updatedAt ? book.libraryItem.updatedAt : book.updatedAt
return book.libraryItem.updatedAt if (updatedAt > mostRecent) {
return updatedAt
} }
return mostRecent return mostRecent
}, 0) }, 0)

View File

@ -107,6 +107,9 @@ class Feed extends Model {
entityUpdatedAt = libraryItem.media.podcastEpisodes.reduce((mostRecent, episode) => { entityUpdatedAt = libraryItem.media.podcastEpisodes.reduce((mostRecent, episode) => {
return episode.updatedAt > mostRecent ? episode.updatedAt : mostRecent return episode.updatedAt > mostRecent ? episode.updatedAt : mostRecent
}, entityUpdatedAt) }, entityUpdatedAt)
} else if (libraryItem.media.updatedAt > entityUpdatedAt) {
// Book feeds will use Book.updatedAt if more recent
entityUpdatedAt = libraryItem.media.updatedAt
} }
const feedObj = { const feedObj = {
@ -472,6 +475,8 @@ class Feed extends Model {
/** @type {typeof import('./FeedEpisode')} */ /** @type {typeof import('./FeedEpisode')} */
const feedEpisodeModel = this.sequelize.models.feedEpisode const feedEpisodeModel = this.sequelize.models.feedEpisode
this.feedEpisodes = await this.getFeedEpisodes()
let feedObj = null let feedObj = null
let feedEpisodeCreateFunc = null let feedEpisodeCreateFunc = null
let feedEpisodeCreateFuncEntity = null let feedEpisodeCreateFuncEntity = null
@ -516,17 +521,24 @@ class Feed extends Model {
try { try {
const updatedFeed = await this.update(feedObj, { transaction }) const updatedFeed = await this.update(feedObj, { transaction })
// Remove existing feed episodes const existingFeedEpisodeIds = this.feedEpisodes.map((ep) => ep.id)
await feedEpisodeModel.destroy({
where: {
feedId: this.id
},
transaction
})
// Create new feed episodes // Create new feed episodes
updatedFeed.feedEpisodes = await feedEpisodeCreateFunc(feedEpisodeCreateFuncEntity, updatedFeed, this.slug, transaction) updatedFeed.feedEpisodes = await feedEpisodeCreateFunc(feedEpisodeCreateFuncEntity, updatedFeed, this.slug, transaction)
const newFeedEpisodeIds = updatedFeed.feedEpisodes.map((ep) => ep.id)
const feedEpisodeIdsToRemove = existingFeedEpisodeIds.filter((epid) => !newFeedEpisodeIds.includes(epid))
if (feedEpisodeIdsToRemove.length) {
Logger.info(`[Feed] Removing ${feedEpisodeIdsToRemove.length} episodes from feed ${this.id}`)
await feedEpisodeModel.destroy({
where: {
id: feedEpisodeIdsToRemove
},
transaction
})
}
await transaction.commit() await transaction.commit()
return updatedFeed return updatedFeed

View File

@ -53,9 +53,10 @@ class FeedEpisode extends Model {
* @param {import('./Feed')} feed * @param {import('./Feed')} feed
* @param {string} slug * @param {string} slug
* @param {import('./PodcastEpisode')} episode * @param {import('./PodcastEpisode')} episode
* @param {string} [existingEpisodeId]
*/ */
static getFeedEpisodeObjFromPodcastEpisode(libraryItemExpanded, feed, slug, episode) { static getFeedEpisodeObjFromPodcastEpisode(libraryItemExpanded, feed, slug, episode, existingEpisodeId = null) {
const episodeId = uuidv4() const episodeId = existingEpisodeId || uuidv4()
return { return {
id: episodeId, id: episodeId,
title: episode.title, title: episode.title,
@ -94,11 +95,18 @@ class FeedEpisode extends Model {
libraryItemExpanded.media.podcastEpisodes.sort((a, b) => new Date(a.pubDate) - new Date(b.pubDate)) libraryItemExpanded.media.podcastEpisodes.sort((a, b) => new Date(a.pubDate) - new Date(b.pubDate))
} }
let numExisting = 0
for (const episode of libraryItemExpanded.media.podcastEpisodes) { for (const episode of libraryItemExpanded.media.podcastEpisodes) {
feedEpisodeObjs.push(this.getFeedEpisodeObjFromPodcastEpisode(libraryItemExpanded, feed, slug, episode)) // Check for existing episode by filepath
const existingEpisode = feed.feedEpisodes?.find((feedEpisode) => {
return feedEpisode.filePath === episode.audioFile.metadata.path
})
numExisting = existingEpisode ? numExisting + 1 : numExisting
feedEpisodeObjs.push(this.getFeedEpisodeObjFromPodcastEpisode(libraryItemExpanded, feed, slug, episode, existingEpisode?.id))
} }
Logger.info(`[FeedEpisode] Creating ${feedEpisodeObjs.length} episodes for feed ${feed.id}`) Logger.info(`[FeedEpisode] Upserting ${feedEpisodeObjs.length} episodes for feed ${feed.id} (${numExisting} existing)`)
return this.bulkCreate(feedEpisodeObjs, { transaction }) return this.bulkCreate(feedEpisodeObjs, { transaction, updateOnDuplicate: ['title', 'author', 'description', 'siteURL', 'enclosureURL', 'enclosureType', 'enclosureSize', 'pubDate', 'season', 'episode', 'episodeType', 'duration', 'filePath', 'explicit'] })
} }
/** /**
@ -127,11 +135,12 @@ class FeedEpisode extends Model {
* @param {string} slug * @param {string} slug
* @param {import('./Book').AudioFileObject} audioTrack * @param {import('./Book').AudioFileObject} audioTrack
* @param {boolean} useChapterTitles * @param {boolean} useChapterTitles
* @param {string} [existingEpisodeId]
*/ */
static getFeedEpisodeObjFromAudiobookTrack(book, pubDateStart, feed, slug, audioTrack, useChapterTitles) { static getFeedEpisodeObjFromAudiobookTrack(book, pubDateStart, feed, slug, audioTrack, useChapterTitles, existingEpisodeId = null) {
// Example: <pubDate>Fri, 04 Feb 2015 00:00:00 GMT</pubDate> // Example: <pubDate>Fri, 04 Feb 2015 00:00:00 GMT</pubDate>
let timeOffset = isNaN(audioTrack.index) ? 0 : Number(audioTrack.index) * 1000 // Offset pubdate to ensure correct order let timeOffset = isNaN(audioTrack.index) ? 0 : Number(audioTrack.index) * 1000 // Offset pubdate to ensure correct order
let episodeId = uuidv4() let episodeId = existingEpisodeId || uuidv4()
// e.g. Track 1 will have a pub date before Track 2 // e.g. Track 1 will have a pub date before Track 2
const audiobookPubDate = date.format(new Date(pubDateStart.valueOf() + timeOffset), 'ddd, DD MMM YYYY HH:mm:ss [GMT]') const audiobookPubDate = date.format(new Date(pubDateStart.valueOf() + timeOffset), 'ddd, DD MMM YYYY HH:mm:ss [GMT]')
@ -179,11 +188,18 @@ class FeedEpisode extends Model {
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(libraryItemExpanded.media) const useChapterTitles = this.checkUseChapterTitlesForEpisodes(libraryItemExpanded.media)
const feedEpisodeObjs = [] const feedEpisodeObjs = []
let numExisting = 0
for (const track of libraryItemExpanded.media.trackList) { for (const track of libraryItemExpanded.media.trackList) {
feedEpisodeObjs.push(this.getFeedEpisodeObjFromAudiobookTrack(libraryItemExpanded.media, libraryItemExpanded.createdAt, feed, slug, track, useChapterTitles)) // Check for existing episode by filepath
const existingEpisode = feed.feedEpisodes?.find((episode) => {
return episode.filePath === track.metadata.path
})
numExisting = existingEpisode ? numExisting + 1 : numExisting
feedEpisodeObjs.push(this.getFeedEpisodeObjFromAudiobookTrack(libraryItemExpanded.media, libraryItemExpanded.createdAt, feed, slug, track, useChapterTitles, existingEpisode?.id))
} }
Logger.info(`[FeedEpisode] Creating ${feedEpisodeObjs.length} episodes for feed ${feed.id}`) Logger.info(`[FeedEpisode] Upserting ${feedEpisodeObjs.length} episodes for feed ${feed.id} (${numExisting} existing)`)
return this.bulkCreate(feedEpisodeObjs, { transaction }) return this.bulkCreate(feedEpisodeObjs, { transaction, updateOnDuplicate: ['title', 'author', 'description', 'siteURL', 'enclosureURL', 'enclosureType', 'enclosureSize', 'pubDate', 'season', 'episode', 'episodeType', 'duration', 'filePath', 'explicit'] })
} }
/** /**
@ -200,14 +216,21 @@ class FeedEpisode extends Model {
}).libraryItem.createdAt }).libraryItem.createdAt
const feedEpisodeObjs = [] const feedEpisodeObjs = []
let numExisting = 0
for (const book of books) { for (const book of books) {
const useChapterTitles = this.checkUseChapterTitlesForEpisodes(book) const useChapterTitles = this.checkUseChapterTitlesForEpisodes(book)
for (const track of book.trackList) { for (const track of book.trackList) {
feedEpisodeObjs.push(this.getFeedEpisodeObjFromAudiobookTrack(book, earliestLibraryItemCreatedAt, feed, slug, track, useChapterTitles)) // Check for existing episode by filepath
const existingEpisode = feed.feedEpisodes?.find((episode) => {
return episode.filePath === track.metadata.path
})
numExisting = existingEpisode ? numExisting + 1 : numExisting
feedEpisodeObjs.push(this.getFeedEpisodeObjFromAudiobookTrack(book, earliestLibraryItemCreatedAt, feed, slug, track, useChapterTitles, existingEpisode?.id))
} }
} }
Logger.info(`[FeedEpisode] Creating ${feedEpisodeObjs.length} episodes for feed ${feed.id}`) Logger.info(`[FeedEpisode] Upserting ${feedEpisodeObjs.length} episodes for feed ${feed.id} (${numExisting} existing)`)
return this.bulkCreate(feedEpisodeObjs, { transaction }) return this.bulkCreate(feedEpisodeObjs, { transaction, updateOnDuplicate: ['title', 'author', 'description', 'siteURL', 'enclosureURL', 'enclosureType', 'enclosureSize', 'pubDate', 'season', 'episode', 'episodeType', 'duration', 'filePath', 'explicit'] })
} }
/** /**