mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Remove serverAddress from Feeds and FeedEpisodes URLs
This commit is contained in:
		
							parent
							
								
									3b4a5b8785
								
							
						
					
					
						commit
						9b8e059efe
					
				| @ -10,9 +10,9 @@ | ||||
|         <p class="text-lg font-semibold mb-4">{{ $strings.HeaderRSSFeedIsOpen }}</p> | ||||
| 
 | ||||
|         <div class="w-full relative"> | ||||
|           <ui-text-input v-model="currentFeed.feedUrl" readonly /> | ||||
|           <ui-text-input :value="feedUrl" readonly /> | ||||
| 
 | ||||
|           <span class="material-symbols absolute right-2 bottom-2 p-0.5 text-base transition-transform duration-100 text-gray-300 hover:text-white transform hover:scale-125 cursor-pointer" @click="copyToClipboard(currentFeed.feedUrl)">content_copy</span> | ||||
|           <span class="material-symbols absolute right-2 bottom-2 p-0.5 text-base transition-transform duration-100 text-gray-300 hover:text-white transform hover:scale-125 cursor-pointer" @click="copyToClipboard(feedUrl)">content_copy</span> | ||||
|         </div> | ||||
| 
 | ||||
|         <div v-if="currentFeed.meta" class="mt-5"> | ||||
| @ -111,8 +111,11 @@ export default { | ||||
|     userIsAdminOrUp() { | ||||
|       return this.$store.getters['user/getIsAdminOrUp'] | ||||
|     }, | ||||
|     feedUrl() { | ||||
|       return this.currentFeed ? `${window.origin}${this.$config.routerBasePath}${this.currentFeed.feedUrl}` : '' | ||||
|     }, | ||||
|     demoFeedUrl() { | ||||
|       return `${window.origin}/feed/${this.newFeedSlug}` | ||||
|       return `${window.origin}${this.$config.routerBasePath}/feed/${this.newFeedSlug}` | ||||
|     }, | ||||
|     isHttp() { | ||||
|       return window.origin.startsWith('http://') | ||||
|  | ||||
| @ -5,8 +5,8 @@ | ||||
|         <p class="text-lg font-semibold mb-4">{{ $strings.HeaderRSSFeedGeneral }}</p> | ||||
| 
 | ||||
|         <div class="w-full relative"> | ||||
|           <ui-text-input v-model="feed.feedUrl" readonly /> | ||||
|           <span class="material-symbols absolute right-2 bottom-2 p-0.5 text-base transition-transform duration-100 text-gray-300 hover:text-white transform hover:scale-125 cursor-pointer" @click="copyToClipboard(feed.feedUrl)">content_copy</span> | ||||
|           <ui-text-input :value="feedUrl" readonly /> | ||||
|           <span class="material-symbols absolute right-2 bottom-2 p-0.5 text-base transition-transform duration-100 text-gray-300 hover:text-white transform hover:scale-125 cursor-pointer" @click="copyToClipboard(feedUrl)">content_copy</span> | ||||
|         </div> | ||||
| 
 | ||||
|         <div v-if="feed.meta" class="mt-5"> | ||||
| @ -70,6 +70,9 @@ export default { | ||||
|     }, | ||||
|     _feed() { | ||||
|       return this.feed || {} | ||||
|     }, | ||||
|     feedUrl() { | ||||
|       return this.feed ? `${window.origin}${this.$config.routerBasePath}${this.feed.feedUrl}` : '' | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|  | ||||
| @ -126,7 +126,7 @@ export default { | ||||
|     }, | ||||
|     coverUrl(feed) { | ||||
|       if (!feed.coverPath) return `${this.$config.routerBasePath}/Logo.png` | ||||
|       return `${feed.feedUrl}/cover` | ||||
|       return `${this.$config.routerBasePath}${feed.feedUrl}/cover` | ||||
|     }, | ||||
|     async loadFeeds() { | ||||
|       const data = await this.$axios.$get(`/api/feeds`).catch((err) => { | ||||
|  | ||||
| @ -253,6 +253,10 @@ class Server { | ||||
|     // if RouterBasePath is set, modify all requests to include the base path
 | ||||
|     if (global.RouterBasePath) { | ||||
|       app.use((req, res, next) => { | ||||
|         const host = req.get('host') | ||||
|         const protocol = req.secure || req.get('x-forwarded-proto') === 'https' ? 'https' : 'http' | ||||
|         const prefix = req.url.startsWith(global.RouterBasePath) ? global.RouterBasePath : '' | ||||
|         req.originalHostPrefix = `${protocol}://${host}${prefix}` | ||||
|         if (!req.url.startsWith(global.RouterBasePath)) { | ||||
|           req.url = `${global.RouterBasePath}${req.url}` | ||||
|         } | ||||
|  | ||||
| @ -162,7 +162,7 @@ class RssFeedManager { | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     const xml = feed.buildXml() | ||||
|     const xml = feed.buildXml(req.originalHostPrefix) | ||||
|     res.set('Content-Type', 'text/xml') | ||||
|     res.send(xml) | ||||
|   } | ||||
|  | ||||
							
								
								
									
										74
									
								
								server/migrations/v2.17.5-remove-host-from-feed-urls.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								server/migrations/v2.17.5-remove-host-from-feed-urls.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,74 @@ | ||||
| /** | ||||
|  * @typedef MigrationContext | ||||
|  * @property {import('sequelize').QueryInterface} queryInterface - a suquelize QueryInterface object. | ||||
|  * @property {import('../Logger')} logger - a Logger object. | ||||
|  * | ||||
|  * @typedef MigrationOptions | ||||
|  * @property {MigrationContext} context - an object containing the migration context. | ||||
|  */ | ||||
| 
 | ||||
| const migrationVersion = '2.17.5' | ||||
| const migrationName = `${migrationVersion}-remove-host-from-feed-urls` | ||||
| const loggerPrefix = `[${migrationVersion} migration]` | ||||
| 
 | ||||
| /** | ||||
|  * This upward migration removes the host (serverAddress) from URL columns in the feeds and feedEpisodes tables. | ||||
|  * | ||||
|  * @param {MigrationOptions} options - an object containing the migration context. | ||||
|  * @returns {Promise<void>} - A promise that resolves when the migration is complete. | ||||
|  */ | ||||
| async function up({ context: { queryInterface, logger } }) { | ||||
|   // Upwards migration script
 | ||||
|   logger.info(`${loggerPrefix} UPGRADE BEGIN: ${migrationName}`) | ||||
| 
 | ||||
|   logger.info(`${loggerPrefix} Removing serverAddress from Feeds table URLs`) | ||||
|   await queryInterface.sequelize.query(` | ||||
|     UPDATE Feeds | ||||
|     SET feedUrl = REPLACE(feedUrl, COALESCE(serverAddress, ''), ''), | ||||
|         imageUrl = REPLACE(imageUrl, COALESCE(serverAddress, ''), ''), | ||||
|         siteUrl = REPLACE(siteUrl, COALESCE(serverAddress, ''), ''); | ||||
|   `)
 | ||||
|   logger.info(`${loggerPrefix} Removed serverAddress from Feeds table URLs`) | ||||
| 
 | ||||
|   logger.info(`${loggerPrefix} Removing serverAddress from FeedEpisodes table URLs`) | ||||
|   await queryInterface.sequelize.query(` | ||||
|     UPDATE FeedEpisodes | ||||
|       SET siteUrl = REPLACE(siteUrl, (SELECT COALESCE(serverAddress, '') FROM Feeds WHERE Feeds.id = FeedEpisodes.feedId), ''), | ||||
|           enclosureUrl = REPLACE(enclosureUrl, (SELECT COALESCE(serverAddress, '') FROM Feeds WHERE Feeds.id = FeedEpisodes.feedId), ''); | ||||
|   `)
 | ||||
|   logger.info(`${loggerPrefix} Removed serverAddress from FeedEpisodes table URLs`) | ||||
| 
 | ||||
|   logger.info(`${loggerPrefix} UPGRADE END: ${migrationName}`) | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * This downward migration script adds the host (serverAddress) back to URL columns in the feeds and feedEpisodes tables. | ||||
|  * | ||||
|  * @param {MigrationOptions} options - an object containing the migration context. | ||||
|  * @returns {Promise<void>} - A promise that resolves when the migration is complete. | ||||
|  */ | ||||
| async function down({ context: { queryInterface, logger } }) { | ||||
|   // Downward migration script
 | ||||
|   logger.info(`${loggerPrefix} DOWNGRADE BEGIN: ${migrationName}`) | ||||
| 
 | ||||
|   logger.info(`${loggerPrefix} Adding serverAddress back to Feeds table URLs`) | ||||
|   await queryInterface.sequelize.query(` | ||||
|     UPDATE Feeds | ||||
|     SET feedUrl = COALESCE(serverAddress, '') || feedUrl, | ||||
|         imageUrl = COALESCE(serverAddress, '') || imageUrl, | ||||
|         siteUrl = COALESCE(serverAddress, '') || siteUrl; | ||||
|   `)
 | ||||
|   logger.info(`${loggerPrefix} Added serverAddress back to Feeds table URLs`) | ||||
| 
 | ||||
|   logger.info(`${loggerPrefix} Adding serverAddress back to FeedEpisodes table URLs`) | ||||
|   await queryInterface.sequelize.query(` | ||||
|     UPDATE FeedEpisodes | ||||
|       SET siteUrl = (SELECT COALESCE(serverAddress, '') || FeedEpisodes.siteUrl FROM Feeds WHERE Feeds.id = FeedEpisodes.feedId), | ||||
|           enclosureUrl = (SELECT COALESCE(serverAddress, '') || FeedEpisodes.enclosureUrl FROM Feeds WHERE Feeds.id = FeedEpisodes.feedId); | ||||
|   `)
 | ||||
|   logger.info(`${loggerPrefix} Added serverAddress back to FeedEpisodes table URLs`) | ||||
| 
 | ||||
|   logger.info(`${loggerPrefix} DOWNGRADE END: ${migrationName}`) | ||||
| } | ||||
| 
 | ||||
| module.exports = { up, down } | ||||
| @ -109,7 +109,7 @@ class Feed { | ||||
|     const mediaMetadata = media.metadata | ||||
|     const isPodcast = libraryItem.mediaType === 'podcast' | ||||
| 
 | ||||
|     const feedUrl = `${serverAddress}/feed/${slug}` | ||||
|     const feedUrl = `/feed/${slug}` | ||||
|     const author = isPodcast ? mediaMetadata.author : mediaMetadata.authorName | ||||
| 
 | ||||
|     this.id = uuidv4() | ||||
| @ -128,9 +128,9 @@ class Feed { | ||||
|     this.meta.title = mediaMetadata.title | ||||
|     this.meta.description = mediaMetadata.description | ||||
|     this.meta.author = author | ||||
|     this.meta.imageUrl = media.coverPath ? `${serverAddress}/feed/${slug}/cover${coverFileExtension}` : `${serverAddress}/Logo.png` | ||||
|     this.meta.imageUrl = media.coverPath ? `/feed/${slug}/cover${coverFileExtension}` : `/Logo.png` | ||||
|     this.meta.feedUrl = feedUrl | ||||
|     this.meta.link = `${serverAddress}/item/${libraryItem.id}` | ||||
|     this.meta.link = `/item/${libraryItem.id}` | ||||
|     this.meta.explicit = !!mediaMetadata.explicit | ||||
|     this.meta.type = mediaMetadata.type | ||||
|     this.meta.language = mediaMetadata.language | ||||
| @ -176,7 +176,7 @@ class Feed { | ||||
|     this.meta.title = mediaMetadata.title | ||||
|     this.meta.description = mediaMetadata.description | ||||
|     this.meta.author = author | ||||
|     this.meta.imageUrl = media.coverPath ? `${this.serverAddress}/feed/${this.slug}/cover${coverFileExtension}` : `${this.serverAddress}/Logo.png` | ||||
|     this.meta.imageUrl = media.coverPath ? `/feed/${this.slug}/cover${coverFileExtension}` : `/Logo.png` | ||||
|     this.meta.explicit = !!mediaMetadata.explicit | ||||
|     this.meta.type = mediaMetadata.type | ||||
|     this.meta.language = mediaMetadata.language | ||||
| @ -206,7 +206,7 @@ class Feed { | ||||
|   } | ||||
| 
 | ||||
|   setFromCollection(userId, slug, collectionExpanded, serverAddress, preventIndexing = true, ownerName = null, ownerEmail = null) { | ||||
|     const feedUrl = `${serverAddress}/feed/${slug}` | ||||
|     const feedUrl = `/feed/${slug}` | ||||
| 
 | ||||
|     const itemsWithTracks = collectionExpanded.books.filter((libraryItem) => libraryItem.media.tracks.length) | ||||
|     const firstItemWithCover = itemsWithTracks.find((item) => item.media.coverPath) | ||||
| @ -227,9 +227,9 @@ class Feed { | ||||
|     this.meta.title = collectionExpanded.name | ||||
|     this.meta.description = collectionExpanded.description || '' | ||||
|     this.meta.author = this.getAuthorsStringFromLibraryItems(itemsWithTracks) | ||||
|     this.meta.imageUrl = this.coverPath ? `${serverAddress}/feed/${slug}/cover${coverFileExtension}` : `${serverAddress}/Logo.png` | ||||
|     this.meta.imageUrl = this.coverPath ? `/feed/${slug}/cover${coverFileExtension}` : `/Logo.png` | ||||
|     this.meta.feedUrl = feedUrl | ||||
|     this.meta.link = `${serverAddress}/collection/${collectionExpanded.id}` | ||||
|     this.meta.link = `/collection/${collectionExpanded.id}` | ||||
|     this.meta.explicit = !!itemsWithTracks.some((li) => li.media.metadata.explicit) // explicit if any item is explicit
 | ||||
|     this.meta.preventIndexing = preventIndexing | ||||
|     this.meta.ownerName = ownerName | ||||
| @ -272,7 +272,7 @@ class Feed { | ||||
|     this.meta.title = collectionExpanded.name | ||||
|     this.meta.description = collectionExpanded.description || '' | ||||
|     this.meta.author = this.getAuthorsStringFromLibraryItems(itemsWithTracks) | ||||
|     this.meta.imageUrl = this.coverPath ? `${this.serverAddress}/feed/${this.slug}/cover${coverFileExtension}` : `${this.serverAddress}/Logo.png` | ||||
|     this.meta.imageUrl = this.coverPath ? `/feed/${this.slug}/cover${coverFileExtension}` : `/Logo.png` | ||||
|     this.meta.explicit = !!itemsWithTracks.some((li) => li.media.metadata.explicit) // explicit if any item is explicit
 | ||||
| 
 | ||||
|     this.episodes = [] | ||||
| @ -301,7 +301,7 @@ class Feed { | ||||
|   } | ||||
| 
 | ||||
|   setFromSeries(userId, slug, seriesExpanded, serverAddress, preventIndexing = true, ownerName = null, ownerEmail = null) { | ||||
|     const feedUrl = `${serverAddress}/feed/${slug}` | ||||
|     const feedUrl = `/feed/${slug}` | ||||
| 
 | ||||
|     let itemsWithTracks = seriesExpanded.books.filter((libraryItem) => libraryItem.media.tracks.length) | ||||
|     // Sort series items by series sequence
 | ||||
| @ -326,9 +326,9 @@ class Feed { | ||||
|     this.meta.title = seriesExpanded.name | ||||
|     this.meta.description = seriesExpanded.description || '' | ||||
|     this.meta.author = this.getAuthorsStringFromLibraryItems(itemsWithTracks) | ||||
|     this.meta.imageUrl = this.coverPath ? `${serverAddress}/feed/${slug}/cover${coverFileExtension}` : `${serverAddress}/Logo.png` | ||||
|     this.meta.imageUrl = this.coverPath ? `/feed/${slug}/cover${coverFileExtension}` : `/Logo.png` | ||||
|     this.meta.feedUrl = feedUrl | ||||
|     this.meta.link = `${serverAddress}/library/${libraryId}/series/${seriesExpanded.id}` | ||||
|     this.meta.link = `/library/${libraryId}/series/${seriesExpanded.id}` | ||||
|     this.meta.explicit = !!itemsWithTracks.some((li) => li.media.metadata.explicit) // explicit if any item is explicit
 | ||||
|     this.meta.preventIndexing = preventIndexing | ||||
|     this.meta.ownerName = ownerName | ||||
| @ -374,7 +374,7 @@ class Feed { | ||||
|     this.meta.title = seriesExpanded.name | ||||
|     this.meta.description = seriesExpanded.description || '' | ||||
|     this.meta.author = this.getAuthorsStringFromLibraryItems(itemsWithTracks) | ||||
|     this.meta.imageUrl = this.coverPath ? `${this.serverAddress}/feed/${this.slug}/cover${coverFileExtension}` : `${this.serverAddress}/Logo.png` | ||||
|     this.meta.imageUrl = this.coverPath ? `/feed/${this.slug}/cover${coverFileExtension}` : `/Logo.png` | ||||
|     this.meta.explicit = !!itemsWithTracks.some((li) => li.media.metadata.explicit) // explicit if any item is explicit
 | ||||
| 
 | ||||
|     this.episodes = [] | ||||
| @ -402,12 +402,12 @@ class Feed { | ||||
|     this.xml = null | ||||
|   } | ||||
| 
 | ||||
|   buildXml() { | ||||
|   buildXml(originalHostPrefix) { | ||||
|     if (this.xml) return this.xml | ||||
| 
 | ||||
|     var rssfeed = new RSS(this.meta.getRSSData()) | ||||
|     var rssfeed = new RSS(this.meta.getRSSData(originalHostPrefix)) | ||||
|     this.episodes.forEach((ep) => { | ||||
|       rssfeed.item(ep.getRSSData()) | ||||
|       rssfeed.item(ep.getRSSData(originalHostPrefix)) | ||||
|     }) | ||||
|     this.xml = rssfeed.xml() | ||||
|     return this.xml | ||||
|  | ||||
| @ -79,7 +79,7 @@ class FeedEpisode { | ||||
|     this.title = episode.title | ||||
|     this.description = episode.description || '' | ||||
|     this.enclosure = { | ||||
|       url: `${serverAddress}${contentUrl}`, | ||||
|       url: `${contentUrl}`, | ||||
|       type: episode.audioTrack.mimeType, | ||||
|       size: episode.size | ||||
|     } | ||||
| @ -136,7 +136,7 @@ class FeedEpisode { | ||||
|     this.title = title | ||||
|     this.description = mediaMetadata.description || '' | ||||
|     this.enclosure = { | ||||
|       url: `${serverAddress}${contentUrl}`, | ||||
|       url: `${contentUrl}`, | ||||
|       type: audioTrack.mimeType, | ||||
|       size: audioTrack.metadata.size | ||||
|     } | ||||
| @ -151,15 +151,19 @@ class FeedEpisode { | ||||
|     this.fullPath = audioTrack.metadata.path | ||||
|   } | ||||
| 
 | ||||
|   getRSSData() { | ||||
|   getRSSData(hostPrefix) { | ||||
|     return { | ||||
|       title: this.title, | ||||
|       description: this.description || '', | ||||
|       url: this.link, | ||||
|       guid: this.enclosure.url, | ||||
|       url: `${hostPrefix}${this.link}`, | ||||
|       guid: `${hostPrefix}${this.enclosure.url}`, | ||||
|       author: this.author, | ||||
|       date: this.pubDate, | ||||
|       enclosure: this.enclosure, | ||||
|       enclosure: { | ||||
|         url: `${hostPrefix}${this.enclosure.url}`, | ||||
|         type: this.enclosure.type, | ||||
|         size: this.enclosure.size | ||||
|       }, | ||||
|       custom_elements: [ | ||||
|         { 'itunes:author': this.author }, | ||||
|         { 'itunes:duration': secondsToTimestamp(this.duration) }, | ||||
|  | ||||
| @ -60,42 +60,36 @@ class FeedMeta { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   getRSSData() { | ||||
|     const blockTags = [ | ||||
|       { 'itunes:block': 'yes' }, | ||||
|       { 'googleplay:block': 'yes' } | ||||
|     ] | ||||
|   getRSSData(hostPrefix) { | ||||
|     const blockTags = [{ 'itunes:block': 'yes' }, { 'googleplay:block': 'yes' }] | ||||
|     return { | ||||
|       title: this.title, | ||||
|       description: this.description || '', | ||||
|       generator: 'Audiobookshelf', | ||||
|       feed_url: this.feedUrl, | ||||
|       site_url: this.link, | ||||
|       image_url: this.imageUrl, | ||||
|       feed_url: `${hostPrefix}${this.feedUrl}`, | ||||
|       site_url: `${hostPrefix}${this.link}`, | ||||
|       image_url: `${hostPrefix}${this.imageUrl}`, | ||||
|       custom_namespaces: { | ||||
|         'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd', | ||||
|         'psc': 'http://podlove.org/simple-chapters', | ||||
|         'podcast': 'https://podcastindex.org/namespace/1.0', | ||||
|         'googleplay': 'http://www.google.com/schemas/play-podcasts/1.0' | ||||
|         itunes: 'http://www.itunes.com/dtds/podcast-1.0.dtd', | ||||
|         psc: 'http://podlove.org/simple-chapters', | ||||
|         podcast: 'https://podcastindex.org/namespace/1.0', | ||||
|         googleplay: 'http://www.google.com/schemas/play-podcasts/1.0' | ||||
|       }, | ||||
|       custom_elements: [ | ||||
|         { 'language': this.language || 'en' }, | ||||
|         { 'author': this.author || 'advplyr' }, | ||||
|         { language: this.language || 'en' }, | ||||
|         { author: this.author || 'advplyr' }, | ||||
|         { 'itunes:author': this.author || 'advplyr' }, | ||||
|         { 'itunes:summary': this.description || '' }, | ||||
|         { 'itunes:type': this.type }, | ||||
|         { | ||||
|           'itunes:image': { | ||||
|             _attr: { | ||||
|               href: this.imageUrl | ||||
|               href: `${hostPrefix}${this.imageUrl}` | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           'itunes:owner': [ | ||||
|             { 'itunes:name': this.ownerName || this.author || '' }, | ||||
|             { 'itunes:email': this.ownerEmail || '' } | ||||
|           ] | ||||
|           'itunes:owner': [{ 'itunes:name': this.ownerName || this.author || '' }, { 'itunes:email': this.ownerEmail || '' }] | ||||
|         }, | ||||
|         { 'itunes:explicit': !!this.explicit }, | ||||
|         ...(this.preventIndexing ? blockTags : []) | ||||
|  | ||||
| @ -0,0 +1,202 @@ | ||||
| const { expect } = require('chai') | ||||
| const sinon = require('sinon') | ||||
| const { up, down } = require('../../../server/migrations/v2.17.5-remove-host-from-feed-urls') | ||||
| const { Sequelize, DataTypes } = require('sequelize') | ||||
| const Logger = require('../../../server/Logger') | ||||
| 
 | ||||
| const defineModels = (sequelize) => { | ||||
|   const Feeds = sequelize.define('Feeds', { | ||||
|     id: { type: DataTypes.UUID, primaryKey: true, defaultValue: DataTypes.UUIDV4 }, | ||||
|     feedUrl: { type: DataTypes.STRING }, | ||||
|     imageUrl: { type: DataTypes.STRING }, | ||||
|     siteUrl: { type: DataTypes.STRING }, | ||||
|     serverAddress: { type: DataTypes.STRING } | ||||
|   }) | ||||
| 
 | ||||
|   const FeedEpisodes = sequelize.define('FeedEpisodes', { | ||||
|     id: { type: DataTypes.UUID, primaryKey: true, defaultValue: DataTypes.UUIDV4 }, | ||||
|     feedId: { type: DataTypes.UUID }, | ||||
|     siteUrl: { type: DataTypes.STRING }, | ||||
|     enclosureUrl: { type: DataTypes.STRING } | ||||
|   }) | ||||
| 
 | ||||
|   return { Feeds, FeedEpisodes } | ||||
| } | ||||
| 
 | ||||
| describe('Migration v2.17.4-use-subfolder-for-oidc-redirect-uris', () => { | ||||
|   let queryInterface, logger, context | ||||
|   let sequelize | ||||
|   let Feeds, FeedEpisodes | ||||
|   const feed1Id = '00000000-0000-4000-a000-000000000001' | ||||
|   const feed2Id = '00000000-0000-4000-a000-000000000002' | ||||
|   const feedEpisode1Id = '00000000-4000-a000-0000-000000000011' | ||||
|   const feedEpisode2Id = '00000000-4000-a000-0000-000000000012' | ||||
|   const feedEpisode3Id = '00000000-4000-a000-0000-000000000021' | ||||
| 
 | ||||
|   before(async () => { | ||||
|     sequelize = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false }) | ||||
|     queryInterface = sequelize.getQueryInterface() | ||||
|     ;({ Feeds, FeedEpisodes } = defineModels(sequelize)) | ||||
|     await sequelize.sync() | ||||
|   }) | ||||
| 
 | ||||
|   after(async () => { | ||||
|     await sequelize.close() | ||||
|   }) | ||||
| 
 | ||||
|   beforeEach(async () => { | ||||
|     // Reset tables before each test
 | ||||
|     await Feeds.destroy({ where: {}, truncate: true }) | ||||
|     await FeedEpisodes.destroy({ where: {}, truncate: true }) | ||||
| 
 | ||||
|     logger = { | ||||
|       info: sinon.stub(), | ||||
|       error: sinon.stub() | ||||
|     } | ||||
|     context = { queryInterface, logger } | ||||
|   }) | ||||
| 
 | ||||
|   describe('up', () => { | ||||
|     it('should remove serverAddress from URLs in Feeds and FeedEpisodes tables', async () => { | ||||
|       await Feeds.bulkCreate([ | ||||
|         { id: feed1Id, feedUrl: 'http://server1.com/feed1', imageUrl: 'http://server1.com/img1', siteUrl: 'http://server1.com/site1', serverAddress: 'http://server1.com' }, | ||||
|         { id: feed2Id, feedUrl: 'http://server2.com/feed2', imageUrl: 'http://server2.com/img2', siteUrl: 'http://server2.com/site2', serverAddress: 'http://server2.com' } | ||||
|       ]) | ||||
| 
 | ||||
|       await FeedEpisodes.bulkCreate([ | ||||
|         { id: feedEpisode1Id, feedId: feed1Id, siteUrl: 'http://server1.com/episode11', enclosureUrl: 'http://server1.com/enclosure11' }, | ||||
|         { id: feedEpisode2Id, feedId: feed1Id, siteUrl: 'http://server1.com/episode12', enclosureUrl: 'http://server1.com/enclosure12' }, | ||||
|         { id: feedEpisode3Id, feedId: feed2Id, siteUrl: 'http://server2.com/episode21', enclosureUrl: 'http://server2.com/enclosure21' } | ||||
|       ]) | ||||
| 
 | ||||
|       await up({ context }) | ||||
|       const feeds = await Feeds.findAll({ raw: true }) | ||||
|       const feedEpisodes = await FeedEpisodes.findAll({ raw: true }) | ||||
| 
 | ||||
|       expect(logger.info.calledWith('[2.17.5 migration] UPGRADE BEGIN: 2.17.5-remove-host-from-feed-urls')).to.be.true | ||||
|       expect(logger.info.calledWith('[2.17.5 migration] Removing serverAddress from Feeds table URLs')).to.be.true | ||||
| 
 | ||||
|       expect(feeds[0].feedUrl).to.equal('/feed1') | ||||
|       expect(feeds[0].imageUrl).to.equal('/img1') | ||||
|       expect(feeds[0].siteUrl).to.equal('/site1') | ||||
|       expect(feeds[1].feedUrl).to.equal('/feed2') | ||||
|       expect(feeds[1].imageUrl).to.equal('/img2') | ||||
|       expect(feeds[1].siteUrl).to.equal('/site2') | ||||
| 
 | ||||
|       expect(logger.info.calledWith('[2.17.5 migration] Removed serverAddress from Feeds table URLs')).to.be.true | ||||
|       expect(logger.info.calledWith('[2.17.5 migration] Removing serverAddress from FeedEpisodes table URLs')).to.be.true | ||||
| 
 | ||||
|       expect(feedEpisodes[0].siteUrl).to.equal('/episode11') | ||||
|       expect(feedEpisodes[0].enclosureUrl).to.equal('/enclosure11') | ||||
|       expect(feedEpisodes[1].siteUrl).to.equal('/episode12') | ||||
|       expect(feedEpisodes[1].enclosureUrl).to.equal('/enclosure12') | ||||
|       expect(feedEpisodes[2].siteUrl).to.equal('/episode21') | ||||
|       expect(feedEpisodes[2].enclosureUrl).to.equal('/enclosure21') | ||||
| 
 | ||||
|       expect(logger.info.calledWith('[2.17.5 migration] Removed serverAddress from FeedEpisodes table URLs')).to.be.true | ||||
|       expect(logger.info.calledWith('[2.17.5 migration] UPGRADE END: 2.17.5-remove-host-from-feed-urls')).to.be.true | ||||
|     }) | ||||
| 
 | ||||
|     it('should handle null URLs in Feeds and FeedEpisodes tables', async () => { | ||||
|       await Feeds.bulkCreate([{ id: feed1Id, feedUrl: 'http://server1.com/feed1', imageUrl: null, siteUrl: 'http://server1.com/site1', serverAddress: 'http://server1.com' }]) | ||||
| 
 | ||||
|       await FeedEpisodes.bulkCreate([{ id: feedEpisode1Id, feedId: feed1Id, siteUrl: null, enclosureUrl: 'http://server1.com/enclosure11' }]) | ||||
| 
 | ||||
|       await up({ context }) | ||||
|       const feeds = await Feeds.findAll({ raw: true }) | ||||
|       const feedEpisodes = await FeedEpisodes.findAll({ raw: true }) | ||||
| 
 | ||||
|       expect(feeds[0].feedUrl).to.equal('/feed1') | ||||
|       expect(feeds[0].imageUrl).to.be.null | ||||
|       expect(feeds[0].siteUrl).to.equal('/site1') | ||||
|       expect(feedEpisodes[0].siteUrl).to.be.null | ||||
|       expect(feedEpisodes[0].enclosureUrl).to.equal('/enclosure11') | ||||
|     }) | ||||
| 
 | ||||
|     it('should handle null serverAddress in Feeds table', async () => { | ||||
|       await Feeds.bulkCreate([{ id: feed1Id, feedUrl: 'http://server1.com/feed1', imageUrl: 'http://server1.com/img1', siteUrl: 'http://server1.com/site1', serverAddress: null }]) | ||||
|       await FeedEpisodes.bulkCreate([{ id: feedEpisode1Id, feedId: feed1Id, siteUrl: 'http://server1.com/episode11', enclosureUrl: 'http://server1.com/enclosure11' }]) | ||||
| 
 | ||||
|       await up({ context }) | ||||
|       const feeds = await Feeds.findAll({ raw: true }) | ||||
|       const feedEpisodes = await FeedEpisodes.findAll({ raw: true }) | ||||
| 
 | ||||
|       expect(feeds[0].feedUrl).to.equal('http://server1.com/feed1') | ||||
|       expect(feeds[0].imageUrl).to.equal('http://server1.com/img1') | ||||
|       expect(feeds[0].siteUrl).to.equal('http://server1.com/site1') | ||||
|       expect(feedEpisodes[0].siteUrl).to.equal('http://server1.com/episode11') | ||||
|       expect(feedEpisodes[0].enclosureUrl).to.equal('http://server1.com/enclosure11') | ||||
|     }) | ||||
|   }) | ||||
| 
 | ||||
|   describe('down', () => { | ||||
|     it('should add serverAddress back to URLs in Feeds and FeedEpisodes tables', async () => { | ||||
|       await Feeds.bulkCreate([ | ||||
|         { id: feed1Id, feedUrl: '/feed1', imageUrl: '/img1', siteUrl: '/site1', serverAddress: 'http://server1.com' }, | ||||
|         { id: feed2Id, feedUrl: '/feed2', imageUrl: '/img2', siteUrl: '/site2', serverAddress: 'http://server2.com' } | ||||
|       ]) | ||||
| 
 | ||||
|       await FeedEpisodes.bulkCreate([ | ||||
|         { id: feedEpisode1Id, feedId: feed1Id, siteUrl: '/episode11', enclosureUrl: '/enclosure11' }, | ||||
|         { id: feedEpisode2Id, feedId: feed1Id, siteUrl: '/episode12', enclosureUrl: '/enclosure12' }, | ||||
|         { id: feedEpisode3Id, feedId: feed2Id, siteUrl: '/episode21', enclosureUrl: '/enclosure21' } | ||||
|       ]) | ||||
| 
 | ||||
|       await down({ context }) | ||||
|       const feeds = await Feeds.findAll({ raw: true }) | ||||
|       const feedEpisodes = await FeedEpisodes.findAll({ raw: true }) | ||||
| 
 | ||||
|       expect(logger.info.calledWith('[2.17.5 migration] DOWNGRADE BEGIN: 2.17.5-remove-host-from-feed-urls')).to.be.true | ||||
|       expect(logger.info.calledWith('[2.17.5 migration] Adding serverAddress back to Feeds table URLs')).to.be.true | ||||
| 
 | ||||
|       expect(feeds[0].feedUrl).to.equal('http://server1.com/feed1') | ||||
|       expect(feeds[0].imageUrl).to.equal('http://server1.com/img1') | ||||
|       expect(feeds[0].siteUrl).to.equal('http://server1.com/site1') | ||||
|       expect(feeds[1].feedUrl).to.equal('http://server2.com/feed2') | ||||
|       expect(feeds[1].imageUrl).to.equal('http://server2.com/img2') | ||||
|       expect(feeds[1].siteUrl).to.equal('http://server2.com/site2') | ||||
| 
 | ||||
|       expect(logger.info.calledWith('[2.17.5 migration] Added serverAddress back to Feeds table URLs')).to.be.true | ||||
|       expect(logger.info.calledWith('[2.17.5 migration] Adding serverAddress back to FeedEpisodes table URLs')).to.be.true | ||||
| 
 | ||||
|       expect(feedEpisodes[0].siteUrl).to.equal('http://server1.com/episode11') | ||||
|       expect(feedEpisodes[0].enclosureUrl).to.equal('http://server1.com/enclosure11') | ||||
|       expect(feedEpisodes[1].siteUrl).to.equal('http://server1.com/episode12') | ||||
|       expect(feedEpisodes[1].enclosureUrl).to.equal('http://server1.com/enclosure12') | ||||
|       expect(feedEpisodes[2].siteUrl).to.equal('http://server2.com/episode21') | ||||
|       expect(feedEpisodes[2].enclosureUrl).to.equal('http://server2.com/enclosure21') | ||||
| 
 | ||||
|       expect(logger.info.calledWith('[2.17.5 migration] DOWNGRADE END: 2.17.5-remove-host-from-feed-urls')).to.be.true | ||||
|     }) | ||||
| 
 | ||||
|     it('should handle null URLs in Feeds and FeedEpisodes tables', async () => { | ||||
|       await Feeds.bulkCreate([{ id: feed1Id, feedUrl: '/feed1', imageUrl: null, siteUrl: '/site1', serverAddress: 'http://server1.com' }]) | ||||
|       await FeedEpisodes.bulkCreate([{ id: feedEpisode1Id, feedId: feed1Id, siteUrl: null, enclosureUrl: '/enclosure11' }]) | ||||
| 
 | ||||
|       await down({ context }) | ||||
|       const feeds = await Feeds.findAll({ raw: true }) | ||||
|       const feedEpisodes = await FeedEpisodes.findAll({ raw: true }) | ||||
| 
 | ||||
|       expect(feeds[0].feedUrl).to.equal('http://server1.com/feed1') | ||||
|       expect(feeds[0].imageUrl).to.be.null | ||||
|       expect(feeds[0].siteUrl).to.equal('http://server1.com/site1') | ||||
|       expect(feedEpisodes[0].siteUrl).to.be.null | ||||
|       expect(feedEpisodes[0].enclosureUrl).to.equal('http://server1.com/enclosure11') | ||||
|     }) | ||||
| 
 | ||||
|     it('should handle null serverAddress in Feeds table', async () => { | ||||
|       await Feeds.bulkCreate([{ id: feed1Id, feedUrl: '/feed1', imageUrl: '/img1', siteUrl: '/site1', serverAddress: null }]) | ||||
|       await FeedEpisodes.bulkCreate([{ id: feedEpisode1Id, feedId: feed1Id, siteUrl: '/episode11', enclosureUrl: '/enclosure11' }]) | ||||
| 
 | ||||
|       await down({ context }) | ||||
|       const feeds = await Feeds.findAll({ raw: true }) | ||||
|       const feedEpisodes = await FeedEpisodes.findAll({ raw: true }) | ||||
| 
 | ||||
|       expect(feeds[0].feedUrl).to.equal('/feed1') | ||||
|       expect(feeds[0].imageUrl).to.equal('/img1') | ||||
|       expect(feeds[0].siteUrl).to.equal('/site1') | ||||
|       expect(feedEpisodes[0].siteUrl).to.equal('/episode11') | ||||
|       expect(feedEpisodes[0].enclosureUrl).to.equal('/enclosure11') | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user