Skip to content

Commit 726adbb

Browse files
authored
Merge pull request #3692 from mikiher/rss-remove-server-address
Remove serverAddress from Feeds and FeedEpisodes URLs
2 parents 5eca430 + f7b7b85 commit 726adbb

File tree

11 files changed

+359
-65
lines changed

11 files changed

+359
-65
lines changed

client/components/modals/rssfeed/OpenCloseModal.vue

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
<p class="text-lg font-semibold mb-4">{{ $strings.HeaderRSSFeedIsOpen }}</p>
1111

1212
<div class="w-full relative">
13-
<ui-text-input v-model="currentFeed.feedUrl" readonly />
13+
<ui-text-input :value="feedUrl" readonly />
1414

15-
<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>
15+
<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>
1616
</div>
1717

1818
<div v-if="currentFeed.meta" class="mt-5">
@@ -111,8 +111,11 @@ export default {
111111
userIsAdminOrUp() {
112112
return this.$store.getters['user/getIsAdminOrUp']
113113
},
114+
feedUrl() {
115+
return this.currentFeed ? `${window.origin}${this.$config.routerBasePath}${this.currentFeed.feedUrl}` : ''
116+
},
114117
demoFeedUrl() {
115-
return `${window.origin}/feed/${this.newFeedSlug}`
118+
return `${window.origin}${this.$config.routerBasePath}/feed/${this.newFeedSlug}`
116119
},
117120
isHttp() {
118121
return window.origin.startsWith('http://')

client/components/modals/rssfeed/ViewFeedModal.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
<p class="text-lg font-semibold mb-4">{{ $strings.HeaderRSSFeedGeneral }}</p>
66

77
<div class="w-full relative">
8-
<ui-text-input v-model="feed.feedUrl" readonly />
9-
<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>
8+
<ui-text-input :value="feedUrl" readonly />
9+
<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>
1010
</div>
1111

1212
<div v-if="feed.meta" class="mt-5">
@@ -70,6 +70,9 @@ export default {
7070
},
7171
_feed() {
7272
return this.feed || {}
73+
},
74+
feedUrl() {
75+
return this.feed ? `${window.origin}${this.$config.routerBasePath}${this.feed.feedUrl}` : ''
7376
}
7477
},
7578
methods: {

client/pages/config/rss-feeds.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ export default {
126126
},
127127
coverUrl(feed) {
128128
if (!feed.coverPath) return `${this.$config.routerBasePath}/Logo.png`
129-
return `${feed.feedUrl}/cover`
129+
return `${this.$config.routerBasePath}${feed.feedUrl}/cover`
130130
},
131131
async loadFeeds() {
132132
const data = await this.$axios.$get(`/api/feeds`).catch((err) => {

server/Server.js

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -251,14 +251,17 @@ class Server {
251251

252252
const router = express.Router()
253253
// if RouterBasePath is set, modify all requests to include the base path
254-
if (global.RouterBasePath) {
255-
app.use((req, res, next) => {
256-
if (!req.url.startsWith(global.RouterBasePath)) {
257-
req.url = `${global.RouterBasePath}${req.url}`
258-
}
259-
next()
260-
})
261-
}
254+
app.use((req, res, next) => {
255+
const urlStartsWithRouterBasePath = req.url.startsWith(global.RouterBasePath)
256+
const host = req.get('host')
257+
const protocol = req.secure || req.get('x-forwarded-proto') === 'https' ? 'https' : 'http'
258+
const prefix = urlStartsWithRouterBasePath ? global.RouterBasePath : ''
259+
req.originalHostPrefix = `${protocol}://${host}${prefix}`
260+
if (!urlStartsWithRouterBasePath) {
261+
req.url = `${global.RouterBasePath}${req.url}`
262+
}
263+
next()
264+
})
262265
app.use(global.RouterBasePath, router)
263266
app.disable('x-powered-by')
264267

server/managers/RssFeedManager.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const { Request, Response } = require('express')
12
const Path = require('path')
23

34
const Logger = require('../Logger')
@@ -77,6 +78,12 @@ class RssFeedManager {
7778
return Database.feedModel.findByPkOld(id)
7879
}
7980

81+
/**
82+
* GET: /feed/:slug
83+
*
84+
* @param {Request} req
85+
* @param {Response} res
86+
*/
8087
async getFeed(req, res) {
8188
const feed = await this.findFeedBySlug(req.params.slug)
8289
if (!feed) {
@@ -162,11 +169,17 @@ class RssFeedManager {
162169
}
163170
}
164171

165-
const xml = feed.buildXml()
172+
const xml = feed.buildXml(req.originalHostPrefix)
166173
res.set('Content-Type', 'text/xml')
167174
res.send(xml)
168175
}
169176

177+
/**
178+
* GET: /feed/:slug/item/:episodeId/*
179+
*
180+
* @param {Request} req
181+
* @param {Response} res
182+
*/
170183
async getFeedItem(req, res) {
171184
const feed = await this.findFeedBySlug(req.params.slug)
172185
if (!feed) {
@@ -183,6 +196,12 @@ class RssFeedManager {
183196
res.sendFile(episodePath)
184197
}
185198

199+
/**
200+
* GET: /feed/:slug/cover*
201+
*
202+
* @param {Request} req
203+
* @param {Response} res
204+
*/
186205
async getFeedCover(req, res) {
187206
const feed = await this.findFeedBySlug(req.params.slug)
188207
if (!feed) {

server/migrations/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ Please add a record of every database migration that you create to this file. Th
1010
| v2.17.0 | v2.17.0-uuid-replacement | Changes the data type of columns with UUIDv4 to UUID matching the associated model |
1111
| v2.17.3 | v2.17.3-fk-constraints | Changes the foreign key constraints for tables due to sequelize bug dropping constraints in v2.17.0 migration |
1212
| v2.17.4 | v2.17.4-use-subfolder-for-oidc-redirect-uris | Save subfolder to OIDC redirect URIs to support existing installations |
13+
| v2.17.5 | v2.17.5-remove-host-from-feed-urls | removes the host (serverAddress) from URL columns in the feeds and feedEpisodes tables |
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* @typedef MigrationContext
3+
* @property {import('sequelize').QueryInterface} queryInterface - a suquelize QueryInterface object.
4+
* @property {import('../Logger')} logger - a Logger object.
5+
*
6+
* @typedef MigrationOptions
7+
* @property {MigrationContext} context - an object containing the migration context.
8+
*/
9+
10+
const migrationVersion = '2.17.5'
11+
const migrationName = `${migrationVersion}-remove-host-from-feed-urls`
12+
const loggerPrefix = `[${migrationVersion} migration]`
13+
14+
/**
15+
* This upward migration removes the host (serverAddress) from URL columns in the feeds and feedEpisodes tables.
16+
*
17+
* @param {MigrationOptions} options - an object containing the migration context.
18+
* @returns {Promise<void>} - A promise that resolves when the migration is complete.
19+
*/
20+
async function up({ context: { queryInterface, logger } }) {
21+
// Upwards migration script
22+
logger.info(`${loggerPrefix} UPGRADE BEGIN: ${migrationName}`)
23+
24+
logger.info(`${loggerPrefix} Removing serverAddress from Feeds table URLs`)
25+
await queryInterface.sequelize.query(`
26+
UPDATE Feeds
27+
SET feedUrl = REPLACE(feedUrl, COALESCE(serverAddress, ''), ''),
28+
imageUrl = REPLACE(imageUrl, COALESCE(serverAddress, ''), ''),
29+
siteUrl = REPLACE(siteUrl, COALESCE(serverAddress, ''), '');
30+
`)
31+
logger.info(`${loggerPrefix} Removed serverAddress from Feeds table URLs`)
32+
33+
logger.info(`${loggerPrefix} Removing serverAddress from FeedEpisodes table URLs`)
34+
await queryInterface.sequelize.query(`
35+
UPDATE FeedEpisodes
36+
SET siteUrl = REPLACE(siteUrl, (SELECT COALESCE(serverAddress, '') FROM Feeds WHERE Feeds.id = FeedEpisodes.feedId), ''),
37+
enclosureUrl = REPLACE(enclosureUrl, (SELECT COALESCE(serverAddress, '') FROM Feeds WHERE Feeds.id = FeedEpisodes.feedId), '');
38+
`)
39+
logger.info(`${loggerPrefix} Removed serverAddress from FeedEpisodes table URLs`)
40+
41+
logger.info(`${loggerPrefix} UPGRADE END: ${migrationName}`)
42+
}
43+
44+
/**
45+
* This downward migration script adds the host (serverAddress) back to URL columns in the feeds and feedEpisodes tables.
46+
*
47+
* @param {MigrationOptions} options - an object containing the migration context.
48+
* @returns {Promise<void>} - A promise that resolves when the migration is complete.
49+
*/
50+
async function down({ context: { queryInterface, logger } }) {
51+
// Downward migration script
52+
logger.info(`${loggerPrefix} DOWNGRADE BEGIN: ${migrationName}`)
53+
54+
logger.info(`${loggerPrefix} Adding serverAddress back to Feeds table URLs`)
55+
await queryInterface.sequelize.query(`
56+
UPDATE Feeds
57+
SET feedUrl = COALESCE(serverAddress, '') || feedUrl,
58+
imageUrl = COALESCE(serverAddress, '') || imageUrl,
59+
siteUrl = COALESCE(serverAddress, '') || siteUrl;
60+
`)
61+
logger.info(`${loggerPrefix} Added serverAddress back to Feeds table URLs`)
62+
63+
logger.info(`${loggerPrefix} Adding serverAddress back to FeedEpisodes table URLs`)
64+
await queryInterface.sequelize.query(`
65+
UPDATE FeedEpisodes
66+
SET siteUrl = (SELECT COALESCE(serverAddress, '') || FeedEpisodes.siteUrl FROM Feeds WHERE Feeds.id = FeedEpisodes.feedId),
67+
enclosureUrl = (SELECT COALESCE(serverAddress, '') || FeedEpisodes.enclosureUrl FROM Feeds WHERE Feeds.id = FeedEpisodes.feedId);
68+
`)
69+
logger.info(`${loggerPrefix} Added serverAddress back to FeedEpisodes table URLs`)
70+
71+
logger.info(`${loggerPrefix} DOWNGRADE END: ${migrationName}`)
72+
}
73+
74+
module.exports = { up, down }

server/objects/Feed.js

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,6 @@ class Feed {
2929
this.createdAt = null
3030
this.updatedAt = null
3131

32-
// Cached xml
33-
this.xml = null
34-
3532
if (feed) {
3633
this.construct(feed)
3734
}
@@ -109,7 +106,7 @@ class Feed {
109106
const mediaMetadata = media.metadata
110107
const isPodcast = libraryItem.mediaType === 'podcast'
111108

112-
const feedUrl = `${serverAddress}/feed/${slug}`
109+
const feedUrl = `/feed/${slug}`
113110
const author = isPodcast ? mediaMetadata.author : mediaMetadata.authorName
114111

115112
this.id = uuidv4()
@@ -128,9 +125,9 @@ class Feed {
128125
this.meta.title = mediaMetadata.title
129126
this.meta.description = mediaMetadata.description
130127
this.meta.author = author
131-
this.meta.imageUrl = media.coverPath ? `${serverAddress}/feed/${slug}/cover${coverFileExtension}` : `${serverAddress}/Logo.png`
128+
this.meta.imageUrl = media.coverPath ? `/feed/${slug}/cover${coverFileExtension}` : `/Logo.png`
132129
this.meta.feedUrl = feedUrl
133-
this.meta.link = `${serverAddress}/item/${libraryItem.id}`
130+
this.meta.link = `/item/${libraryItem.id}`
134131
this.meta.explicit = !!mediaMetadata.explicit
135132
this.meta.type = mediaMetadata.type
136133
this.meta.language = mediaMetadata.language
@@ -176,7 +173,7 @@ class Feed {
176173
this.meta.title = mediaMetadata.title
177174
this.meta.description = mediaMetadata.description
178175
this.meta.author = author
179-
this.meta.imageUrl = media.coverPath ? `${this.serverAddress}/feed/${this.slug}/cover${coverFileExtension}` : `${this.serverAddress}/Logo.png`
176+
this.meta.imageUrl = media.coverPath ? `/feed/${this.slug}/cover${coverFileExtension}` : `/Logo.png`
180177
this.meta.explicit = !!mediaMetadata.explicit
181178
this.meta.type = mediaMetadata.type
182179
this.meta.language = mediaMetadata.language
@@ -202,11 +199,10 @@ class Feed {
202199
}
203200

204201
this.updatedAt = Date.now()
205-
this.xml = null
206202
}
207203

208204
setFromCollection(userId, slug, collectionExpanded, serverAddress, preventIndexing = true, ownerName = null, ownerEmail = null) {
209-
const feedUrl = `${serverAddress}/feed/${slug}`
205+
const feedUrl = `/feed/${slug}`
210206

211207
const itemsWithTracks = collectionExpanded.books.filter((libraryItem) => libraryItem.media.tracks.length)
212208
const firstItemWithCover = itemsWithTracks.find((item) => item.media.coverPath)
@@ -227,9 +223,9 @@ class Feed {
227223
this.meta.title = collectionExpanded.name
228224
this.meta.description = collectionExpanded.description || ''
229225
this.meta.author = this.getAuthorsStringFromLibraryItems(itemsWithTracks)
230-
this.meta.imageUrl = this.coverPath ? `${serverAddress}/feed/${slug}/cover${coverFileExtension}` : `${serverAddress}/Logo.png`
226+
this.meta.imageUrl = this.coverPath ? `/feed/${slug}/cover${coverFileExtension}` : `/Logo.png`
231227
this.meta.feedUrl = feedUrl
232-
this.meta.link = `${serverAddress}/collection/${collectionExpanded.id}`
228+
this.meta.link = `/collection/${collectionExpanded.id}`
233229
this.meta.explicit = !!itemsWithTracks.some((li) => li.media.metadata.explicit) // explicit if any item is explicit
234230
this.meta.preventIndexing = preventIndexing
235231
this.meta.ownerName = ownerName
@@ -272,7 +268,7 @@ class Feed {
272268
this.meta.title = collectionExpanded.name
273269
this.meta.description = collectionExpanded.description || ''
274270
this.meta.author = this.getAuthorsStringFromLibraryItems(itemsWithTracks)
275-
this.meta.imageUrl = this.coverPath ? `${this.serverAddress}/feed/${this.slug}/cover${coverFileExtension}` : `${this.serverAddress}/Logo.png`
271+
this.meta.imageUrl = this.coverPath ? `/feed/${this.slug}/cover${coverFileExtension}` : `/Logo.png`
276272
this.meta.explicit = !!itemsWithTracks.some((li) => li.media.metadata.explicit) // explicit if any item is explicit
277273

278274
this.episodes = []
@@ -297,11 +293,10 @@ class Feed {
297293
})
298294

299295
this.updatedAt = Date.now()
300-
this.xml = null
301296
}
302297

303298
setFromSeries(userId, slug, seriesExpanded, serverAddress, preventIndexing = true, ownerName = null, ownerEmail = null) {
304-
const feedUrl = `${serverAddress}/feed/${slug}`
299+
const feedUrl = `/feed/${slug}`
305300

306301
let itemsWithTracks = seriesExpanded.books.filter((libraryItem) => libraryItem.media.tracks.length)
307302
// Sort series items by series sequence
@@ -326,9 +321,9 @@ class Feed {
326321
this.meta.title = seriesExpanded.name
327322
this.meta.description = seriesExpanded.description || ''
328323
this.meta.author = this.getAuthorsStringFromLibraryItems(itemsWithTracks)
329-
this.meta.imageUrl = this.coverPath ? `${serverAddress}/feed/${slug}/cover${coverFileExtension}` : `${serverAddress}/Logo.png`
324+
this.meta.imageUrl = this.coverPath ? `/feed/${slug}/cover${coverFileExtension}` : `/Logo.png`
330325
this.meta.feedUrl = feedUrl
331-
this.meta.link = `${serverAddress}/library/${libraryId}/series/${seriesExpanded.id}`
326+
this.meta.link = `/library/${libraryId}/series/${seriesExpanded.id}`
332327
this.meta.explicit = !!itemsWithTracks.some((li) => li.media.metadata.explicit) // explicit if any item is explicit
333328
this.meta.preventIndexing = preventIndexing
334329
this.meta.ownerName = ownerName
@@ -374,7 +369,7 @@ class Feed {
374369
this.meta.title = seriesExpanded.name
375370
this.meta.description = seriesExpanded.description || ''
376371
this.meta.author = this.getAuthorsStringFromLibraryItems(itemsWithTracks)
377-
this.meta.imageUrl = this.coverPath ? `${this.serverAddress}/feed/${this.slug}/cover${coverFileExtension}` : `${this.serverAddress}/Logo.png`
372+
this.meta.imageUrl = this.coverPath ? `/feed/${this.slug}/cover${coverFileExtension}` : `/Logo.png`
378373
this.meta.explicit = !!itemsWithTracks.some((li) => li.media.metadata.explicit) // explicit if any item is explicit
379374

380375
this.episodes = []
@@ -399,18 +394,14 @@ class Feed {
399394
})
400395

401396
this.updatedAt = Date.now()
402-
this.xml = null
403397
}
404398

405-
buildXml() {
406-
if (this.xml) return this.xml
407-
408-
var rssfeed = new RSS(this.meta.getRSSData())
399+
buildXml(originalHostPrefix) {
400+
var rssfeed = new RSS(this.meta.getRSSData(originalHostPrefix))
409401
this.episodes.forEach((ep) => {
410-
rssfeed.item(ep.getRSSData())
402+
rssfeed.item(ep.getRSSData(originalHostPrefix))
411403
})
412-
this.xml = rssfeed.xml()
413-
return this.xml
404+
return rssfeed.xml()
414405
}
415406

416407
getAuthorsStringFromLibraryItems(libraryItems) {

server/objects/FeedEpisode.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class FeedEpisode {
7979
this.title = episode.title
8080
this.description = episode.description || ''
8181
this.enclosure = {
82-
url: `${serverAddress}${contentUrl}`,
82+
url: `${contentUrl}`,
8383
type: episode.audioTrack.mimeType,
8484
size: episode.size
8585
}
@@ -136,7 +136,7 @@ class FeedEpisode {
136136
this.title = title
137137
this.description = mediaMetadata.description || ''
138138
this.enclosure = {
139-
url: `${serverAddress}${contentUrl}`,
139+
url: `${contentUrl}`,
140140
type: audioTrack.mimeType,
141141
size: audioTrack.metadata.size
142142
}
@@ -151,15 +151,19 @@ class FeedEpisode {
151151
this.fullPath = audioTrack.metadata.path
152152
}
153153

154-
getRSSData() {
154+
getRSSData(hostPrefix) {
155155
return {
156156
title: this.title,
157157
description: this.description || '',
158-
url: this.link,
159-
guid: this.enclosure.url,
158+
url: `${hostPrefix}${this.link}`,
159+
guid: `${hostPrefix}${this.enclosure.url}`,
160160
author: this.author,
161161
date: this.pubDate,
162-
enclosure: this.enclosure,
162+
enclosure: {
163+
url: `${hostPrefix}${this.enclosure.url}`,
164+
type: this.enclosure.type,
165+
size: this.enclosure.size
166+
},
163167
custom_elements: [
164168
{ 'itunes:author': this.author },
165169
{ 'itunes:duration': secondsToTimestamp(this.duration) },

0 commit comments

Comments
 (0)