From 5c4271045e838601f2146284272e4a5aaaea15c4 Mon Sep 17 00:00:00 2001 From: Greg Lorenzen Date: Tue, 10 Dec 2024 06:31:41 +0000 Subject: [PATCH] Add download route to share controller and public router --- server/controllers/ShareController.js | 70 +++++++++++++++++++++++++++ server/routers/PublicRouter.js | 1 + 2 files changed, 71 insertions(+) diff --git a/server/controllers/ShareController.js b/server/controllers/ShareController.js index 7cd2af218..129d7da50 100644 --- a/server/controllers/ShareController.js +++ b/server/controllers/ShareController.js @@ -7,6 +7,7 @@ const Database = require('../Database') const { PlayMethod } = require('../utils/constants') const { getAudioMimeTypeFromExtname, encodeUriPath } = require('../utils/fileUtils') +const zipHelpers = require('../utils/zipHelpers') const PlaybackSession = require('../objects/PlaybackSession') const ShareManager = require('../managers/ShareManager') @@ -210,6 +211,75 @@ class ShareController { res.sendFile(audioTrackPath) } + /** + * Public route + * + * GET: /api/share/:slug/download + * Downloads media item share + * + * @param {Request} req + * @param {Response} res + */ + async downloadMediaItemShare(req, res) { + const { slug } = req.params + + // Find matching MediaItemShare based on slug + const mediaItemShare = await ShareManager.findBySlug(slug) + // If the file isDownloadable, download the file + if (mediaItemShare.isDownloadable) { + // Get mediaItemId and type + const { mediaItemId, mediaItemType } = mediaItemShare + + // Get the library item from the mediaItemId + const libraryItem = await Database.libraryItemModel.findOne({ + where: { + mediaId: mediaItemId + } + }) + + const itemPath = libraryItem.path + const itemRelPath = libraryItem.relPath + let itemTitle + + // Get the title based on the mediaItemType + if (mediaItemType === 'podcastEpisode') { + const podcastEpisode = await Database.podcastEpisodeModel.findOne({ + where: { + id: mediaItemId + } + }) + itemTitle = podcastEpisode.title + } else if (mediaItemType === 'book') { + const book = await Database.bookModel.findOne({ + where: { + id: mediaItemId + } + }) + itemTitle = book.title + } + + Logger.info(`[ShareController] Requested download for book "${itemTitle}" at "${itemPath}"`) + + try { + if (libraryItem.isFile) { + const audioMimeType = getAudioMimeTypeFromExtname(Path.extname(itemPath)) + if (audioMimeType) { + res.setHeader('Content-Type', audioMimeType) + } + await new Promise((resolve, reject) => res.download(itemPath, itemRelPath, (error) => (error ? reject(error) : resolve()))) + } else { + const filename = `${itemTitle}.zip` + await zipHelpers.zipDirectoryPipe(itemPath, filename, res) + } + + Logger.info(`[ShareController] Downloaded item "${itemTitle}" at "${itemPath}"`) + } catch (error) { + Logger.error(`[ShareController] Download failed for item "${itemTitle}" at "${itemPath}"`, error) + res.status(500).send('Failed to download the item') + } + } + } + /** * Public route - requires share_session_id cookie * diff --git a/server/routers/PublicRouter.js b/server/routers/PublicRouter.js index 98ac4955a..107edf99c 100644 --- a/server/routers/PublicRouter.js +++ b/server/routers/PublicRouter.js @@ -15,6 +15,7 @@ class PublicRouter { this.router.get('/share/:slug', ShareController.getMediaItemShareBySlug.bind(this)) this.router.get('/share/:slug/track/:index', ShareController.getMediaItemShareAudioTrack.bind(this)) this.router.get('/share/:slug/cover', ShareController.getMediaItemShareCoverImage.bind(this)) + this.router.get('/share/:slug/download', ShareController.downloadMediaItemShare.bind(this)) this.router.patch('/share/:slug/progress', ShareController.updateMediaItemShareProgress.bind(this)) } }