Update Share download endpoint & share modal UI

This commit is contained in:
advplyr 2024-12-29 16:25:27 -06:00
parent 3668f4a366
commit 85d32a1606
3 changed files with 75 additions and 56 deletions

View File

@ -19,12 +19,13 @@
<ui-text-input v-model="currentShareUrl" show-copy readonly class="text-base h-10" /> <ui-text-input v-model="currentShareUrl" show-copy readonly class="text-base h-10" />
</div> </div>
<div class="w-full py-2 px-1"> <div class="w-full py-2 px-1">
<p v-if="currentShare.expiresAt" class="text-base">{{ $getString('MessageShareExpiresIn', [currentShareTimeRemaining]) }}</p> <p v-if="currentShare.isDownloadable" class="text-sm mb-2">{{ $strings.LabelDownloadable }}</p>
<p v-if="currentShare.expiresAt">{{ $getString('MessageShareExpiresIn', [currentShareTimeRemaining]) }}</p>
<p v-else>{{ $strings.LabelPermanent }}</p> <p v-else>{{ $strings.LabelPermanent }}</p>
</div> </div>
</template> </template>
<template v-else> <template v-else>
<div class="flex flex-col sm:flex-row items-center justify-between space-y-4 sm:space-y-0 sm:space-x-4 mb-4"> <div class="flex flex-col sm:flex-row items-center justify-between space-y-4 sm:space-y-0 sm:space-x-4 mb-2">
<div class="w-full sm:w-48"> <div class="w-full sm:w-48">
<label class="px-1 text-sm font-semibold block">{{ $strings.LabelSlug }}</label> <label class="px-1 text-sm font-semibold block">{{ $strings.LabelSlug }}</label>
<ui-text-input v-model="newShareSlug" class="text-base h-10" /> <ui-text-input v-model="newShareSlug" class="text-base h-10" />
@ -46,9 +47,7 @@
</div> </div>
</div> </div>
</div> </div>
<p class="text-sm text-gray-300 py-1 px-1" v-html="$getString('MessageShareURLWillBe', [demoShareUrl])" /> <div class="flex items-center w-full md:w-1/2 mb-4">
<p class="text-sm text-gray-300 py-1 px-1" v-html="$getString('MessageShareExpirationWillBe', [expirationDateString])" />
<div class="flex items-center w-full md:w-1/2">
<p class="text-sm text-gray-300 py-1 px-1">{{ $strings.LabelDownloadable }}</p> <p class="text-sm text-gray-300 py-1 px-1">{{ $strings.LabelDownloadable }}</p>
<ui-toggle-switch size="sm" v-model="isDownloadable" /> <ui-toggle-switch size="sm" v-model="isDownloadable" />
<ui-tooltip :text="$strings.LabelShareDownloadableHelp"> <ui-tooltip :text="$strings.LabelShareDownloadableHelp">
@ -57,6 +56,8 @@
</p> </p>
</ui-tooltip> </ui-tooltip>
</div> </div>
<p class="text-sm text-gray-300 py-1 px-1" v-html="$getString('MessageShareURLWillBe', [demoShareUrl])" />
<p class="text-sm text-gray-300 py-1 px-1" v-html="$getString('MessageShareExpirationWillBe', [expirationDateString])" />
</template> </template>
<div class="flex items-center pt-6"> <div class="flex items-center pt-6">
<div class="flex-grow" /> <div class="flex-grow" />

View File

@ -212,7 +212,7 @@ class ShareController {
} }
/** /**
* Public route * Public route - requires share_session_id cookie
* *
* GET: /api/share/:slug/download * GET: /api/share/:slug/download
* Downloads media item share * Downloads media item share
@ -221,42 +221,33 @@ class ShareController {
* @param {Response} res * @param {Response} res
*/ */
async downloadMediaItemShare(req, res) { async downloadMediaItemShare(req, res) {
const { slug } = req.params if (!req.cookies.share_session_id) {
return res.status(404).send('Share session not set')
// 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 { slug } = req.params
const mediaItemShare = ShareManager.findBySlug(slug)
if (!mediaItemShare) {
return res.status(404)
}
if (!mediaItemShare.isDownloadable) {
return res.status(403).send('Download is not allowed for this item')
}
const playbackSession = ShareManager.findPlaybackSessionBySessionId(req.cookies.share_session_id)
if (!playbackSession || playbackSession.mediaItemShareId !== mediaItemShare.id) {
return res.status(404).send('Share session not found')
}
const libraryItem = await Database.libraryItemModel.findByPk(playbackSession.libraryItemId, {
attributes: ['id', 'path', 'relPath', 'isFile']
}) })
if (!libraryItem) {
return res.status(404).send('Library item not found')
}
const itemPath = libraryItem.path const itemPath = libraryItem.path
const itemRelPath = libraryItem.relPath const itemTitle = playbackSession.displayTitle
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}"`) Logger.info(`[ShareController] Requested download for book "${itemTitle}" at "${itemPath}"`)
@ -266,7 +257,7 @@ class ShareController {
if (audioMimeType) { if (audioMimeType) {
res.setHeader('Content-Type', audioMimeType) res.setHeader('Content-Type', audioMimeType)
} }
await new Promise((resolve, reject) => res.download(itemPath, itemRelPath, (error) => (error ? reject(error) : resolve()))) await new Promise((resolve, reject) => res.download(itemPath, libraryItem.relPath, (error) => (error ? reject(error) : resolve())))
} else { } else {
const filename = `${itemTitle}.zip` const filename = `${itemTitle}.zip`
await zipHelpers.zipDirectoryPipe(itemPath, filename, res) await zipHelpers.zipDirectoryPipe(itemPath, filename, res)
@ -278,7 +269,6 @@ class ShareController {
res.status(500).send('Failed to download the item') res.status(500).send('Failed to download the item')
} }
} }
}
/** /**
* Public route - requires share_session_id cookie * Public route - requires share_session_id cookie

View File

@ -32,6 +32,34 @@ const { DataTypes, Model } = require('sequelize')
class MediaItemShare extends Model { class MediaItemShare extends Model {
constructor(values, options) { constructor(values, options) {
super(values, options) super(values, options)
/** @type {UUIDV4} */
this.id
/** @type {UUIDV4} */
this.mediaItemId
/** @type {string} */
this.mediaItemType
/** @type {string} */
this.slug
/** @type {string} */
this.pash
/** @type {UUIDV4} */
this.userId
/** @type {Date} */
this.expiresAt
/** @type {Object} */
this.extraData
/** @type {Date} */
this.createdAt
/** @type {Date} */
this.updatedAt
/** @type {boolean} */
this.isDownloadable
// Expanded properties
/** @type {import('./Book')|import('./PodcastEpisode')} */
this.mediaItem
} }
toJSONForClient() { toJSONForClient() {