Skip to content

Commit

Permalink
[WIP] feat: Add download limit
Browse files Browse the repository at this point in the history
Signed-off-by: Christopher Ng <chrng8@gmail.com>
  • Loading branch information
Pytal committed Feb 24, 2024
1 parent 30c283d commit f142aa9
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 0 deletions.
14 changes: 14 additions & 0 deletions apps/files_sharing/src/mixins/ShareDetails.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Type as ShareTypes } from '@nextcloud/sharing'
import { getCapabilities } from '@nextcloud/capabilities'

import Share from '../models/Share.js'
import { getDownloadLimit } from '../services/DownloadLimitService.js'

export default {
methods: {
Expand All @@ -19,9 +23,19 @@ export default {
share = this.mapShareRequestToShareObject(shareRequestObject)
}

const isPublicShare = [
ShareTypes.SHARE_TYPE_LINK,
ShareTypes.SHARE_TYPE_EMAIL,
].includes(share.shareType ?? share.type)

const downloadLimit = (getCapabilities()?.downloadlimit?.enabled && isPublicShare && this.fileInfo.type === 'file' && share.token)
? await getDownloadLimit(share.token)
: null

const shareDetails = {
fileInfo: this.fileInfo,
share,
downloadLimit,
}

this.$emit('open-sharing-details', shareDetails)
Expand Down
49 changes: 49 additions & 0 deletions apps/files_sharing/src/services/DownloadLimitService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* @copyright Copyright (c) 2021 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license AGPL-3.0-or-later
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'

type Nullable<T> = null | T

export interface DownloadLimit {
limit: Nullable<number>
count: Nullable<number>
}

export const getDownloadLimit = async (token: string): Promise<DownloadLimit> => {
const response = await axios.get(generateOcsUrl('/apps/files_downloadlimit/api/v1/{token}/limit', { token }))
return response.data.ocs.data
}

export const setDownloadLimit = async (token: string, limit: number) => {
const response = await axios.put(generateOcsUrl('/apps/files_downloadlimit/api/v1/{token}/limit', { token }), {
limit,
})
return response.data.ocs.data
}

export const deleteDownloadLimit = async (token: string) => {
const response = await axios.delete(generateOcsUrl('/apps/files_downloadlimit/api/v1/{token}/limit', { token }))
return response.data.ocs.data
}
113 changes: 113 additions & 0 deletions apps/files_sharing/src/views/SharingDetailsTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,28 @@
:placeholder="t('files_sharing', 'Expiration date')"
type="date"
@input="onExpirationChange" />
<template v-if="isDownloadLimitAllowed">
<NcCheckboxRadioSwitch :checked.sync="isDownloadLimitEnabled">
{{ t('files_sharing', 'Limit downloads') }}
</NcCheckboxRadioSwitch>
<NcNoteCard v-show="isDownloadLimitEnabled && showRemainingDownloads"
class="sharingTabDetailsView__count-note"
type="info">
{{ n('files_sharing', '1 download remaining', '{count} downloads remaining', remainingDownlaodsCount, { count: remainingDownlaodsCount }) }}
</NcNoteCard>
<NcTextField v-show="isDownloadLimitEnabled"
:label="t('settings', 'Set download limit')"
type="number"
min="1"
:value.sync="downloadLimit"
:helper-text="downloadLimitHelperText"
:error="Boolean(downloadLimitHelperText)" />
<NcNoteCard v-show="isDownloadLimitEnabled && !isNewShare"
class="sharingTabDetailsView__reset-note"
type="warning">
{{ t('files_sharing', 'Setting a new limit will reset the download count') }}
</NcNoteCard>
</template>
<NcCheckboxRadioSwitch v-if="isPublicShare"
:disabled="canChangeHideDownload"
:checked.sync="share.hideDownload"
Expand Down Expand Up @@ -215,9 +237,13 @@
<script>
import { getLanguage } from '@nextcloud/l10n'
import { getCapabilities } from '@nextcloud/capabilities'
import { showError } from '@nextcloud/dialogs'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
import NcPasswordField from '@nextcloud/vue/dist/Components/NcPasswordField.js'
import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
import NcDateTimePickerNative from '@nextcloud/vue/dist/Components/NcDateTimePickerNative.js'
Expand All @@ -242,6 +268,7 @@ import Share from '../models/Share.js'
import ShareRequests from '../mixins/ShareRequests.js'
import ShareTypes from '../mixins/ShareTypes.js'
import SharesMixin from '../mixins/SharesMixin.js'
import { deleteDownloadLimit, setDownloadLimit } from '../services/DownloadLimitService.js'
import {
ATOMIC_PERMISSIONS,
Expand All @@ -255,6 +282,8 @@ export default {
NcAvatar,
NcButton,
NcInputField,
NcTextField,
NcNoteCard,
NcPasswordField,
NcDateTimePickerNative,
NcCheckboxRadioSwitch,
Expand Down Expand Up @@ -286,6 +315,13 @@ export default {
type: Object,
required: true,
},
/**
* @type {import('../services/DownloadLimitService.js').DownloadLimit}
*/
initialDownloadLimit: {
type: Object,
default: null,
},
},
data() {
return {
Expand All @@ -299,6 +335,9 @@ export default {
isFirstComponentLoad: true,
test: false,
creating: false,
isDownloadLimitEnabled: Boolean(this.initialDownloadLimit?.limit),
showRemainingDownloads: typeof this.initialDownloadLimit?.count === 'number',
downloadLimit: this.initialDownloadLimit?.limit ?? '',
}
},
Expand Down Expand Up @@ -425,6 +464,24 @@ export default {
}
},
},
isDownloadLimitAllowed() {
return getCapabilities()?.downloadlimit?.enabled && this.isPublicShare && this.isFile && this.share.token
},
remainingDownlaodsCount() {
return this.initialDownloadLimit?.limit - this.initialDownloadLimit?.count
},
/**
* Is the current share a file ?
*
* @return {boolean}
*/
isFile() {
return this.fileInfo.type === 'file'
},
/**
* Is the current share a folder ?
*
Expand Down Expand Up @@ -651,6 +708,30 @@ export default {
}
return undefined
},
shouldUpdateDownloadLimit() {
const isValidLimit = this.isDownloadLimitAllowed
&& typeof this.downloadLimit === 'number'
if (!isValidLimit) {
return false
}
if (this.isNewShare) {
return true
}
return this.downloadLimit !== this.initialDownloadLimit?.limit
},
shouldDeleteDownloadLimit() {
return this.isDownloadLimitAllowed
&& Boolean(this.initialDownloadLimit?.limit)
},
downloadLimitHelperText() {
if (this.downloadLimit >= 1) {
return ''
}
return t('files_sharing', 'The minimum limit is 1')
},
},
watch: {
setCustomPermissions(isChecked) {
Expand Down Expand Up @@ -841,8 +922,32 @@ export default {
this.queueUpdate(...permissionsAndAttributes)
}
await this.saveDownloadLimit()
this.$emit('close-sharing-details')
},
async saveDownloadLimit() {
// TODO Test
if (this.isDownloadLimitEnabled) {
if (this.shouldUpdateDownloadLimit) {
try {
await setDownloadLimit(this.share.token, this.downloadLimit)
} catch (error) {
showError(t('files_sharing', 'Failed to save download limit'))
}
}
return
}
if (this.shouldDeleteDownloadLimit) {
try {
await deleteDownloadLimit(this.share.token)
} catch (error) {
showError(t('files_sharing', 'Failed to remove download limit'))
}
}
},
/**
* Process the new share request
*
Expand Down Expand Up @@ -1062,6 +1167,14 @@ export default {
}
}
&__count-note {
margin-top: 4px;
}
&__reset-note {
margin-bottom: 8px;
}
&__delete {
>button:first-child {
color: rgb(223, 7, 7);
Expand Down
2 changes: 2 additions & 0 deletions apps/files_sharing/src/views/SharingTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
<SharingDetailsTab v-if="showSharingDetailsView"
:file-info="shareDetailsData.fileInfo"
:share="shareDetailsData.share"
:initial-download-limit="shareDetailsData.downloadLimit"
@close-sharing-details="toggleShareDetailsView"
@add:share="addShare"
@remove:share="removeShare" />
Expand Down Expand Up @@ -191,6 +192,7 @@ export default {
* Get the existing shares infos
*/
async getShares() {
// TODO Fetch here?
try {
this.loading = true
Expand Down

0 comments on commit f142aa9

Please sign in to comment.