diff --git a/frontend/src/components/dialog/generate-upload-link.js b/frontend/src/components/dialog/generate-upload-link.js
index 05a24e9294..1983fc2818 100644
--- a/frontend/src/components/dialog/generate-upload-link.js
+++ b/frontend/src/components/dialog/generate-upload-link.js
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import copy from 'copy-to-clipboard';
+import moment from 'moment';
import { Button, Form, FormGroup, Label, Input, InputGroup, InputGroupAddon, Alert } from 'reactstrap';
import { gettext, shareLinkPasswordMinLength, canSendShareLinkEmail } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
@@ -25,7 +26,9 @@ class GenerateUploadLink extends React.Component {
password: '',
passwdnew: '',
sharedUploadInfo: null,
- isSendLinkShown: false
+ isSendLinkShown: false,
+ isExpireChecked: false,
+ expireDays: 0,
};
}
@@ -90,29 +93,61 @@ class GenerateUploadLink extends React.Component {
generateUploadLink = () => {
let path = this.props.itemPath;
let repoID = this.props.repoID;
+ let { password, expireDays } = this.state;
- if (this.state.showPasswordInput && (this.state.password == '')) {
- this.setState({
- errorInfo: gettext('Please enter password')
- });
- }
- else if (this.state.showPasswordInput && (this.state.showPasswordInput && this.state.password.length < shareLinkPasswordMinLength)) {
- this.setState({
- errorInfo: gettext('Password is too short')
- });
- }
- else if (this.state.showPasswordInput && (this.state.password !== this.state.passwordnew)) {
- this.setState({
- errorInfo: gettext('Passwords don\'t match')
- });
- } else {
- seafileAPI.createUploadLink(repoID, path, this.state.password).then((res) => {
+ let isValid = this.validateParamsInput();
+ if (isValid) {
+ seafileAPI.createUploadLink(repoID, path, password, expireDays).then((res) => {
let sharedUploadInfo = new SharedUploadInfo(res.data);
this.setState({sharedUploadInfo: sharedUploadInfo});
});
}
}
+ validateParamsInput = () => {
+ let { showPasswordInput , password, passwordnew, isExpireChecked, expireDays } = this.state;
+
+ // check password params
+ if (showPasswordInput) {
+ if (password.length === 0) {
+ this.setState({errorInfo: gettext('Please enter password')});
+ return false;
+ }
+ if (password.length < shareLinkPasswordMinLength) {
+ this.setState({errorInfo: gettext('Password is too short')});
+ return false;
+ }
+ if (password !== passwordnew) {
+ this.setState({errorInfo: gettext('Passwords don\'t match')});
+ return false;
+ }
+ }
+
+ // check expire day params
+ let reg = /^\d+$/;
+ if (isExpireChecked) {
+ if (!expireDays) {
+ this.setState({errorInfo: gettext('Please enter days')});
+ return false;
+ }
+ if (!reg.test(expireDays)) {
+ this.setState({errorInfo: gettext('Please enter a non-negative integer')});
+ return false;
+ }
+ this.setState({expireDays: parseInt(expireDays)});
+ }
+ return true;
+ }
+
+ onExpireChecked = (e) => {
+ this.setState({isExpireChecked: e.target.checked});
+ }
+
+ onExpireDaysChanged = (e) => {
+ let day = e.target.value.trim();
+ this.setState({expireDays: day});
+ }
+
onCopyUploadLink = () => {
let uploadLink = this.state.sharedUploadInfo.link;
copy(uploadLink);
@@ -156,6 +191,12 @@ class GenerateUploadLink extends React.Component {
+ {sharedUploadInfo.expire_date && (
+
+ {gettext('Expiration Date:')}
+ {moment(sharedUploadInfo.expire_date).format('YYYY-MM-DD hh:mm:ss')}
+
+ )}
{canSendShareLinkEmail && !isSendLinkShown && }
{!isSendLinkShown && }
@@ -192,6 +233,18 @@ class GenerateUploadLink extends React.Component {
}
+
+
+
+ {this.state.isExpireChecked &&
+
+
+
+ }
{this.state.errorInfo && {this.state.errorInfo}}
diff --git a/frontend/src/models/shared-upload-info.js b/frontend/src/models/shared-upload-info.js
index e9776e62e9..cd7f0f9b8c 100644
--- a/frontend/src/models/shared-upload-info.js
+++ b/frontend/src/models/shared-upload-info.js
@@ -10,6 +10,8 @@ class SharedUploadInfo {
this.ctime = object.ctime;
this.token = object.token;
this.view_cnt = object.view_cnt;
+ this.expire_date = object.expire_date;
+ this.is_expired = object.is_expired;
}
}
diff --git a/frontend/src/pages/share-admin/upload-links.js b/frontend/src/pages/share-admin/upload-links.js
index 97d3efb2ae..130d26b7e3 100644
--- a/frontend/src/pages/share-admin/upload-links.js
+++ b/frontend/src/pages/share-admin/upload-links.js
@@ -1,5 +1,6 @@
-import React, { Component } from 'react';
+import React, { Component, Fragment } from 'react';
import { Link } from '@reach/router';
+import moment from 'moment';
import { Modal, ModalHeader, ModalBody } from 'reactstrap';
import { gettext, siteRoot, loginUrl, canGenerateShareLink } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
@@ -52,9 +53,10 @@ class Content extends Component {
{/*icon*/} |
- {gettext('Name')} |
- {gettext('Library')} |
- {gettext('Visits')} |
+ {gettext('Name')} |
+ {gettext('Library')} |
+ {gettext('Visits')} |
+ {gettext('Expiration')} |
{/*Operations*/} |
@@ -114,6 +116,24 @@ class Item extends Component {
return { iconUrl, uploadUrl };
}
+ renderExpriedData = () => {
+ let item = this.props.item;
+ if (!item.expire_date) {
+ return (
+ --
+ );
+ }
+ let expire_date = moment(item.expire_date).format('YYYY-MM-DD');
+ return (
+
+ {item.is_expired ?
+ {expire_date} :
+ expire_date
+ }
+
+ );
+ }
+
render() {
let item = this.props.item;
let { iconUrl, uploadUrl } = this.getUploadParams();
@@ -128,6 +148,7 @@ class Item extends Component {
{item.obj_name} |
{item.repo_name} |
{item.view_cnt} |
+ {this.renderExpriedData()} |
diff --git a/seahub/api2/endpoints/upload_links.py b/seahub/api2/endpoints/upload_links.py
index 03777cc832..290b0ebff7 100644
--- a/seahub/api2/endpoints/upload_links.py
+++ b/seahub/api2/endpoints/upload_links.py
@@ -2,6 +2,7 @@
import os
import logging
from constance import config
+from dateutil.relativedelta import relativedelta
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
@@ -9,6 +10,7 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
+from django.utils import timezone
from django.utils.translation import ugettext as _
from seaserv import seafile_api
@@ -48,6 +50,11 @@ def get_upload_link_info(uls):
else:
ctime = ''
+ if uls.expire_date:
+ expire_date = datetime_to_isoformat_timestr(uls.expire_date)
+ else:
+ expire_date = ''
+
data['repo_id'] = repo_id
data['repo_name'] = repo.repo_name if repo else ''
data['path'] = path
@@ -57,6 +64,8 @@ def get_upload_link_info(uls):
data['link'] = gen_shared_upload_link(token)
data['token'] = token
data['username'] = uls.username
+ data['expire_date'] = expire_date
+ data['is_expired'] = uls.is_expired()
return data
@@ -143,6 +152,12 @@ class UploadLinks(APIView):
error_msg = _('Password is too short')
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+ try:
+ expire_days = int(request.data.get('expire_days', 0))
+ except ValueError:
+ error_msg = 'expire_days invalid.'
+ return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
+
# resource check
repo = seafile_api.get_repo(repo_id)
if not repo:
@@ -164,11 +179,16 @@ class UploadLinks(APIView):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
+ if expire_days <= 0:
+ expire_date = None
+ else:
+ expire_date = timezone.now() + relativedelta(days=expire_days)
+
username = request.user.username
uls = UploadLinkShare.objects.get_upload_link_by_path(username, repo_id, path)
if not uls:
uls = UploadLinkShare.objects.create_upload_link_share(username,
- repo_id, path, password)
+ repo_id, path, password, expire_date)
link_info = get_upload_link_info(uls)
return Response(link_info)
diff --git a/seahub/share/models.py b/seahub/share/models.py
index 408ab90772..6291cc5683 100644
--- a/seahub/share/models.py
+++ b/seahub/share/models.py
@@ -451,6 +451,12 @@ class UploadLinkShare(models.Model):
def is_owner(self, owner):
return owner == self.username
+ def is_expired(self):
+ if self.expire_date is not None and timezone.now() > self.expire_date:
+ return True
+ else:
+ return False
+
class PrivateFileDirShareManager(models.Manager):
def add_private_file_share(self, from_user, to_user, repo_id, path, perm):
"""
diff --git a/tests/api/endpoints/test_upload_links.py b/tests/api/endpoints/test_upload_links.py
index 98534e9c59..e2337830f3 100644
--- a/tests/api/endpoints/test_upload_links.py
+++ b/tests/api/endpoints/test_upload_links.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
import json
from mock import patch
+from dateutil.relativedelta import relativedelta
+from django.utils import timezone
from django.core.urlresolvers import reverse
@@ -29,9 +31,9 @@ class UploadLinksTest(BaseTestCase):
def tearDown(self):
self.remove_repo()
- def _add_upload_link(self):
+ def _add_upload_link(self, expire_date=None):
upload_link = UploadLinkShare.objects.create_upload_link_share(self.user_name,
- self.repo_id, self.folder_path, None, None)
+ self.repo_id, self.folder_path, None, expire_date=expire_date)
return upload_link.token
@@ -50,6 +52,8 @@ class UploadLinksTest(BaseTestCase):
assert json_resp[0]['link'] is not None
assert json_resp[0]['token'] is not None
+ assert json_resp[0]['is_expired'] is not None
+
assert token in json_resp[0]['link']
assert 'u/d' in json_resp[0]['link']
@@ -58,6 +62,20 @@ class UploadLinksTest(BaseTestCase):
self._remove_upload_link(token)
+ def test_get_expired_upload_link(self):
+ self.login_as(self.user)
+ # create a upload link expired one day ago.
+ expire_date = timezone.now() + relativedelta(days=-1)
+ token = self._add_upload_link(expire_date=expire_date)
+
+ resp = self.client.get(self.url + '?path=' + self.folder_path + '&repo_id=' + self.repo_id)
+ self.assertEqual(200, resp.status_code)
+
+ json_resp = json.loads(resp.content)
+ assert json_resp[0]['is_expired'] == True
+
+ self._remove_upload_link(token)
+
@patch.object(CanGenerateUploadLink, 'has_permission')
def test_get_link_with_invalid_user_role_permission(self, mock_has_permission):
self.login_as(self.user)
|