mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-08-23 13:49:57 +02:00
Update Share download endpoint & share modal UI
This commit is contained in:
parent
3668f4a366
commit
85d32a1606
@ -19,12 +19,13 @@
|
||||
<ui-text-input v-model="currentShareUrl" show-copy readonly class="text-base h-10" />
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
<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">
|
||||
<label class="px-1 text-sm font-semibold block">{{ $strings.LabelSlug }}</label>
|
||||
<ui-text-input v-model="newShareSlug" class="text-base h-10" />
|
||||
@ -46,9 +47,7 @@
|
||||
</div>
|
||||
</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])" />
|
||||
<div class="flex items-center w-full md:w-1/2">
|
||||
<div class="flex items-center w-full md:w-1/2 mb-4">
|
||||
<p class="text-sm text-gray-300 py-1 px-1">{{ $strings.LabelDownloadable }}</p>
|
||||
<ui-toggle-switch size="sm" v-model="isDownloadable" />
|
||||
<ui-tooltip :text="$strings.LabelShareDownloadableHelp">
|
||||
@ -57,6 +56,8 @@
|
||||
</p>
|
||||
</ui-tooltip>
|
||||
</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>
|
||||
<div class="flex items-center pt-6">
|
||||
<div class="flex-grow" />
|
||||
|
@ -212,7 +212,7 @@ class ShareController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Public route
|
||||
* Public route - requires share_session_id cookie
|
||||
*
|
||||
* GET: /api/share/:slug/download
|
||||
* Downloads media item share
|
||||
@ -221,62 +221,52 @@ class ShareController {
|
||||
* @param {Response} res
|
||||
*/
|
||||
async downloadMediaItemShare(req, res) {
|
||||
if (!req.cookies.share_session_id) {
|
||||
return res.status(404).send('Share session not set')
|
||||
}
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
// 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
|
||||
const playbackSession = ShareManager.findPlaybackSessionBySessionId(req.cookies.share_session_id)
|
||||
if (!playbackSession || playbackSession.mediaItemShareId !== mediaItemShare.id) {
|
||||
return res.status(404).send('Share session not found')
|
||||
}
|
||||
|
||||
// Get the library item from the mediaItemId
|
||||
const libraryItem = await Database.libraryItemModel.findOne({
|
||||
where: {
|
||||
mediaId: mediaItemId
|
||||
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 itemTitle = playbackSession.displayTitle
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
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
|
||||
await new Promise((resolve, reject) => res.download(itemPath, libraryItem.relPath, (error) => (error ? reject(error) : resolve())))
|
||||
} else {
|
||||
const filename = `${itemTitle}.zip`
|
||||
await zipHelpers.zipDirectoryPipe(itemPath, filename, res)
|
||||
}
|
||||
|
||||
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')
|
||||
}
|
||||
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')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,34 @@ const { DataTypes, Model } = require('sequelize')
|
||||
class MediaItemShare extends Model {
|
||||
constructor(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() {
|
||||
|
Loading…
Reference in New Issue
Block a user