Skip to content

Commit

Permalink
Update Share download endpoint & share modal UI
Browse files Browse the repository at this point in the history
  • Loading branch information
advplyr committed Dec 29, 2024
1 parent 3668f4a commit 85d32a1
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 54 deletions.
11 changes: 6 additions & 5 deletions client/components/modals/ShareModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand All @@ -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">
Expand All @@ -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" />
Expand Down
88 changes: 39 additions & 49 deletions server/controllers/ShareController.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ class ShareController {
}

/**
* Public route
* Public route - requires share_session_id cookie
*
* GET: /api/share/:slug/download
* Downloads media item share
Expand All @@ -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 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
}
const itemPath = libraryItem.path
const itemTitle = playbackSession.displayTitle

Logger.info(`[ShareController] Requested download for book "${itemTitle}" at "${itemPath}"`)
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)
try {
if (libraryItem.isFile) {
const audioMimeType = getAudioMimeTypeFromExtname(Path.extname(itemPath))
if (audioMimeType) {
res.setHeader('Content-Type', audioMimeType)
}

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')
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] 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')
}
}

Expand Down
28 changes: 28 additions & 0 deletions server/models/MediaItemShare.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down

0 comments on commit 85d32a1

Please sign in to comment.