audiobookshelf/server/controllers/RSSFeedController.js

205 lines
6.9 KiB
JavaScript
Raw Normal View History

const { Request, Response, NextFunction } = require('express')
2022-12-26 23:58:36 +01:00
const Logger = require('../Logger')
2023-07-05 01:14:44 +02:00
const Database = require('../Database')
const RssFeedManager = require('../managers/RssFeedManager')
2022-12-26 23:58:36 +01:00
/**
2024-08-12 00:01:25 +02:00
* @typedef RequestUserObject
* @property {import('../models/User')} user
*
2024-08-12 00:01:25 +02:00
* @typedef {Request & RequestUserObject} RequestWithUser
*/
2022-12-26 23:58:36 +01:00
class RSSFeedController {
constructor() {}
/**
* GET: /api/feeds
*
* @this {import('../routers/ApiRouter')}
*
* @param {RequestWithUser} req
* @param {Response} res
*/
2023-08-22 18:42:55 +02:00
async getAll(req, res) {
const feeds = await RssFeedManager.getFeeds()
2023-08-22 18:42:55 +02:00
res.json({
feeds: feeds.map((f) => f.toJSON()),
minified: feeds.map((f) => f.toJSONMinified())
2023-08-22 18:42:55 +02:00
})
}
/**
* POST: /api/feeds/item/:itemId/open
*
* @this {import('../routers/ApiRouter')}
*
* @param {RequestWithUser} req
* @param {Response} res
*/
2022-12-26 23:58:36 +01:00
async openRSSFeedForItem(req, res) {
const reqBody = req.body || {}
2022-12-26 23:58:36 +01:00
const itemExpanded = await Database.libraryItemModel.getExpandedById(req.params.itemId)
if (!itemExpanded) return res.sendStatus(404)
2022-12-26 23:58:36 +01:00
// Check user can access this library item
if (!req.user.checkCanAccessLibraryItem(itemExpanded)) {
Logger.error(`[RSSFeedController] User "${req.user.username}" attempted to open an RSS feed for item "${itemExpanded.media.title}" that they don\'t have access to`)
2022-12-26 23:58:36 +01:00
return res.sendStatus(403)
}
// Check request body options exist
if (!reqBody.serverAddress || !reqBody.slug || typeof reqBody.serverAddress !== 'string' || typeof reqBody.slug !== 'string') {
2022-12-26 23:58:36 +01:00
Logger.error(`[RSSFeedController] Invalid request body to open RSS feed`)
return res.status(400).send('Invalid request body')
}
// Check item has audio tracks
if (!itemExpanded.hasAudioTracks()) {
Logger.error(`[RSSFeedController] Cannot open RSS feed for item "${itemExpanded.media.title}" because it has no audio tracks`)
2022-12-26 23:58:36 +01:00
return res.status(400).send('Item has no audio tracks')
}
// Check that this slug is not being used for another feed (slug will also be the Feed id)
if (await RssFeedManager.findFeedBySlug(reqBody.slug)) {
Logger.error(`[RSSFeedController] Cannot open RSS feed because slug "${reqBody.slug}" is already in use`)
2022-12-26 23:58:36 +01:00
return res.status(400).send('Slug already in use')
}
const feed = await RssFeedManager.openFeedForItem(req.user.id, itemExpanded, reqBody)
if (!feed) {
Logger.error(`[RSSFeedController] Failed to open RSS feed for item "${itemExpanded.media.title}"`)
return res.status(500).send('Failed to open RSS feed')
}
2022-12-26 23:58:36 +01:00
res.json({
feed: feed.toOldJSONMinified()
2022-12-26 23:58:36 +01:00
})
}
/**
* POST: /api/feeds/collection/:collectionId/open
*
* @this {import('../routers/ApiRouter')}
*
* @param {RequestWithUser} req
* @param {Response} res
*/
async openRSSFeedForCollection(req, res) {
const reqBody = req.body || {}
// Check request body options exist
if (!reqBody.serverAddress || !reqBody.slug || typeof reqBody.serverAddress !== 'string' || typeof reqBody.slug !== 'string') {
Logger.error(`[RSSFeedController] Invalid request body to open RSS feed`)
return res.status(400).send('Invalid request body')
}
// Check that this slug is not being used for another feed (slug will also be the Feed id)
if (await RssFeedManager.findFeedBySlug(reqBody.slug)) {
Logger.error(`[RSSFeedController] Cannot open RSS feed because slug "${reqBody.slug}" is already in use`)
return res.status(400).send('Slug already in use')
}
const collection = await Database.collectionModel.getExpandedById(req.params.collectionId)
if (!collection) return res.sendStatus(404)
// Check collection has audio tracks
if (!collection.books.some((book) => book.includedAudioFiles.length)) {
Logger.error(`[RSSFeedController] Cannot open RSS feed for collection "${collection.name}" because it has no audio tracks`)
return res.status(400).send('Collection has no audio tracks')
}
const feed = await RssFeedManager.openFeedForCollection(req.user.id, collection, reqBody)
if (!feed) {
Logger.error(`[RSSFeedController] Failed to open RSS feed for collection "${collection.name}"`)
return res.status(500).send('Failed to open RSS feed')
}
res.json({
feed: feed.toOldJSONMinified()
})
}
/**
* POST: /api/feeds/series/:seriesId/open
*
* @this {import('../routers/ApiRouter')}
*
* @param {RequestWithUser} req
* @param {Response} res
*/
async openRSSFeedForSeries(req, res) {
const reqBody = req.body || {}
// Check request body options exist
if (!reqBody.serverAddress || !reqBody.slug || typeof reqBody.serverAddress !== 'string' || typeof reqBody.slug !== 'string') {
Logger.error(`[RSSFeedController] Invalid request body to open RSS feed`)
return res.status(400).send('Invalid request body')
}
// Check that this slug is not being used for another feed (slug will also be the Feed id)
if (await RssFeedManager.findFeedBySlug(reqBody.slug)) {
Logger.error(`[RSSFeedController] Cannot open RSS feed because slug "${reqBody.slug}" is already in use`)
return res.status(400).send('Slug already in use')
}
const series = await Database.seriesModel.getExpandedById(req.params.seriesId)
if (!series) return res.sendStatus(404)
// Check series has audio tracks
if (!series.books.some((book) => book.includedAudioFiles.length)) {
Logger.error(`[RSSFeedController] Cannot open RSS feed for series "${series.name}" because it has no audio tracks`)
return res.status(400).send('Series has no audio tracks')
}
const feed = await RssFeedManager.openFeedForSeries(req.user.id, series, req.body)
if (!feed) {
Logger.error(`[RSSFeedController] Failed to open RSS feed for series "${series.name}"`)
return res.status(500).send('Failed to open RSS feed')
}
res.json({
feed: feed.toOldJSONMinified()
})
}
/**
* POST: /api/feeds/:id/close
*
* @this {import('../routers/ApiRouter')}
*
* @param {RequestWithUser} req
* @param {Response} res
*/
async closeRSSFeed(req, res) {
const feed = await Database.feedModel.findByPk(req.params.id)
if (!feed) {
Logger.error(`[RSSFeedController] Cannot close RSS feed because feed "${req.params.id}" does not exist`)
return res.sendStatus(404)
}
await RssFeedManager.handleCloseFeed(feed)
res.sendStatus(200)
2022-12-26 23:58:36 +01:00
}
/**
*
* @param {RequestWithUser} req
* @param {Response} res
* @param {NextFunction} next
*/
2022-12-26 23:58:36 +01:00
middleware(req, res, next) {
if (!req.user.isAdminOrUp) {
// Only admins can manage rss feeds
Logger.error(`[RSSFeedController] Non-admin user "${req.user.username}" attempted to make a request to an RSS feed route`)
2022-12-26 23:58:36 +01:00
return res.sendStatus(403)
}
next()
}
}
2023-02-25 14:20:26 +01:00
module.exports = new RSSFeedController()