mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-10 11:22:09 +00:00
parent
1f25cf459c
commit
00b1007cae
@ -19,6 +19,7 @@ const MSG_TYPE_FILE_COMMENT = 'file_comment';
|
|||||||
const MSG_TYPE_DRAFT_COMMENT = 'draft_comment';
|
const MSG_TYPE_DRAFT_COMMENT = 'draft_comment';
|
||||||
const MSG_TYPE_DRAFT_REVIEWER = 'draft_reviewer';
|
const MSG_TYPE_DRAFT_REVIEWER = 'draft_reviewer';
|
||||||
const MSG_TYPE_GUEST_INVITATION_ACCEPTED = 'guest_invitation_accepted';
|
const MSG_TYPE_GUEST_INVITATION_ACCEPTED = 'guest_invitation_accepted';
|
||||||
|
const MSG_TYPE_REPO_MONITOR = 'repo_monitor';
|
||||||
|
|
||||||
class NoticeItem extends React.Component {
|
class NoticeItem extends React.Component {
|
||||||
|
|
||||||
@ -70,7 +71,7 @@ class NoticeItem extends React.Component {
|
|||||||
notice = notice.replace('{share_from}', shareFrom);
|
notice = notice.replace('{share_from}', shareFrom);
|
||||||
notice = notice.replace('{repo_link}', `{tagA}${repoName}{/tagA}`);
|
notice = notice.replace('{repo_link}', `{tagA}${repoName}{/tagA}`);
|
||||||
notice = Utils.HTMLescape(notice);
|
notice = Utils.HTMLescape(notice);
|
||||||
|
|
||||||
// 3. add jump link
|
// 3. add jump link
|
||||||
notice = notice.replace('{tagA}', `<a href='${Utils.encodePath(repoUrl)}'>`);
|
notice = notice.replace('{tagA}', `<a href='${Utils.encodePath(repoUrl)}'>`);
|
||||||
notice = notice.replace('{/tagA}', '</a>');
|
notice = notice.replace('{/tagA}', '</a>');
|
||||||
@ -98,13 +99,13 @@ class NoticeItem extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
notice = gettext('{share_from} has shared a folder named {repo_link} to group {group_link}.');
|
notice = gettext('{share_from} has shared a folder named {repo_link} to group {group_link}.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. handle xss(cross-site scripting)
|
// 2. handle xss(cross-site scripting)
|
||||||
notice = notice.replace('{share_from}', shareFrom);
|
notice = notice.replace('{share_from}', shareFrom);
|
||||||
notice = notice.replace('{repo_link}', `{tagA}${repoName}{/tagA}`);
|
notice = notice.replace('{repo_link}', `{tagA}${repoName}{/tagA}`);
|
||||||
notice = notice.replace('{group_link}', `{tagB}${groupName}{/tagB}`);
|
notice = notice.replace('{group_link}', `{tagB}${groupName}{/tagB}`);
|
||||||
notice = Utils.HTMLescape(notice);
|
notice = Utils.HTMLescape(notice);
|
||||||
|
|
||||||
// 3. add jump link
|
// 3. add jump link
|
||||||
notice = notice.replace('{tagA}', `<a href='${Utils.encodePath(repoUrl)}'>`);
|
notice = notice.replace('{tagA}', `<a href='${Utils.encodePath(repoUrl)}'>`);
|
||||||
notice = notice.replace('{/tagA}', '</a>');
|
notice = notice.replace('{/tagA}', '</a>');
|
||||||
@ -128,7 +129,7 @@ class NoticeItem extends React.Component {
|
|||||||
notice = notice.replace('{user}', repoOwner);
|
notice = notice.replace('{user}', repoOwner);
|
||||||
notice = notice.replace('{repo_link}', `{tagA}${repoName}{/tagA}`);
|
notice = notice.replace('{repo_link}', `{tagA}${repoName}{/tagA}`);
|
||||||
notice = Utils.HTMLescape(notice);
|
notice = Utils.HTMLescape(notice);
|
||||||
|
|
||||||
// 3. add jump link
|
// 3. add jump link
|
||||||
notice = notice.replace('{tagA}', `<a href=${Utils.encodePath(repoUrl)}>`);
|
notice = notice.replace('{tagA}', `<a href=${Utils.encodePath(repoUrl)}>`);
|
||||||
notice = notice.replace('{/tagA}', '</a>');
|
notice = notice.replace('{/tagA}', '</a>');
|
||||||
@ -151,7 +152,7 @@ class NoticeItem extends React.Component {
|
|||||||
notice = notice.replace('{upload_file_link}', `{tagA}${fileName}{/tagA}`);
|
notice = notice.replace('{upload_file_link}', `{tagA}${fileName}{/tagA}`);
|
||||||
notice = notice.replace('{uploaded_link}', `{tagB}${folderName}{/tagB}`);
|
notice = notice.replace('{uploaded_link}', `{tagB}${folderName}{/tagB}`);
|
||||||
notice = Utils.HTMLescape(notice);
|
notice = Utils.HTMLescape(notice);
|
||||||
|
|
||||||
// 3. add jump link
|
// 3. add jump link
|
||||||
notice = notice.replace('{tagA}', `<a href=${Utils.encodePath(fileLink)}>`);
|
notice = notice.replace('{tagA}', `<a href=${Utils.encodePath(fileLink)}>`);
|
||||||
notice = notice.replace('{/tagA}', '</a>');
|
notice = notice.replace('{/tagA}', '</a>');
|
||||||
@ -180,12 +181,12 @@ class NoticeItem extends React.Component {
|
|||||||
|
|
||||||
// 1. handle translate
|
// 1. handle translate
|
||||||
let notice = gettext('File {file_link} has a new comment form user {author}.');
|
let notice = gettext('File {file_link} has a new comment form user {author}.');
|
||||||
|
|
||||||
// 2. handle xss(cross-site scripting)
|
// 2. handle xss(cross-site scripting)
|
||||||
notice = notice.replace('{file_link}', `{tagA}${fileName}{/tagA}`);
|
notice = notice.replace('{file_link}', `{tagA}${fileName}{/tagA}`);
|
||||||
notice = notice.replace('{author}', author);
|
notice = notice.replace('{author}', author);
|
||||||
notice = Utils.HTMLescape(notice);
|
notice = Utils.HTMLescape(notice);
|
||||||
|
|
||||||
// 3. add jump link
|
// 3. add jump link
|
||||||
notice = notice.replace('{tagA}', `<a href=${Utils.encodePath(fileUrl)}>`);
|
notice = notice.replace('{tagA}', `<a href=${Utils.encodePath(fileUrl)}>`);
|
||||||
notice = notice.replace('{/tagA}', '</a>');
|
notice = notice.replace('{/tagA}', '</a>');
|
||||||
@ -224,6 +225,72 @@ class NoticeItem extends React.Component {
|
|||||||
return {avatar_url, notice};
|
return {avatar_url, notice};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (noticeType === MSG_TYPE_REPO_MONITOR) {
|
||||||
|
let avatar_url = detail.op_user_avatar_url;
|
||||||
|
let repoLink = siteRoot + 'library/' + detail.repo_id + '/' + detail.repo_name + '/';
|
||||||
|
let notice = '';
|
||||||
|
|
||||||
|
let op = '';
|
||||||
|
if (detail.obj_type == 'file') {
|
||||||
|
switch (detail.op_type) {
|
||||||
|
case 'create':
|
||||||
|
op = gettext('created file');
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
op = gettext('deleted file');
|
||||||
|
break;
|
||||||
|
case 'recover':
|
||||||
|
op = gettext('restored file');
|
||||||
|
break;
|
||||||
|
case 'rename':
|
||||||
|
op = gettext('renamed file');
|
||||||
|
break;
|
||||||
|
case 'move':
|
||||||
|
op = gettext('moved file');
|
||||||
|
break;
|
||||||
|
case 'edit':
|
||||||
|
op = gettext('updated file');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else { // dir
|
||||||
|
switch (detail.op_type) {
|
||||||
|
case 'create':
|
||||||
|
op = gettext('created folder');
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
op = gettext('deleted folder');
|
||||||
|
break;
|
||||||
|
case 'recover':
|
||||||
|
op = gettext('restored folder');
|
||||||
|
break;
|
||||||
|
case 'rename':
|
||||||
|
op = gettext('renamed folder');
|
||||||
|
break;
|
||||||
|
case 'move':
|
||||||
|
op = gettext('moved folder');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. handle translate
|
||||||
|
notice = gettext('{op_user} {op_type} {obj_name} in {repo_link}.');
|
||||||
|
|
||||||
|
let obj_name = Utils.getFileName(detail.obj_path_list[0]);
|
||||||
|
|
||||||
|
// 2. handle xss(cross-site scripting)
|
||||||
|
notice = notice.replace('{op_user}', `${detail.op_user_name}`);
|
||||||
|
notice = notice.replace('{op_type}', `${op}`);
|
||||||
|
notice = notice.replace('{obj_name}', `${obj_name}`);
|
||||||
|
notice = notice.replace('{repo_link}', `{tagA}${detail.repo_name}{/tagA}`);
|
||||||
|
notice = Utils.HTMLescape(notice);
|
||||||
|
|
||||||
|
// 3. add jump link
|
||||||
|
notice = notice.replace('{tagA}', `<a href=${Utils.encodePath(repoLink)}>`);
|
||||||
|
notice = notice.replace('{/tagA}', '</a>');
|
||||||
|
|
||||||
|
return {avatar_url, notice};
|
||||||
|
}
|
||||||
|
|
||||||
// if (noticeType === MSG_TYPE_GUEST_INVITATION_ACCEPTED) {
|
// if (noticeType === MSG_TYPE_GUEST_INVITATION_ACCEPTED) {
|
||||||
|
|
||||||
// }
|
// }
|
||||||
|
@ -17,6 +17,7 @@ class Repo {
|
|||||||
this.modifier_name = object.modifier_name;
|
this.modifier_name = object.modifier_name;
|
||||||
this.type = object.type;
|
this.type = object.type;
|
||||||
this.starred = object.starred;
|
this.starred = object.starred;
|
||||||
|
this.monitored = object.monitored;
|
||||||
this.status = object.status;
|
this.status = object.status;
|
||||||
this.storage_name = object.storage_name;
|
this.storage_name = object.storage_name;
|
||||||
if (object.is_admin != undefined) {
|
if (object.is_admin != undefined) {
|
||||||
|
@ -40,6 +40,7 @@ class MylibRepoListItem extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
isOpIconShow: false,
|
isOpIconShow: false,
|
||||||
isStarred: this.props.repo.starred,
|
isStarred: this.props.repo.starred,
|
||||||
|
isMonitored: this.props.repo.monitored,
|
||||||
isRenaming: false,
|
isRenaming: false,
|
||||||
isShareDialogShow: false,
|
isShareDialogShow: false,
|
||||||
isDeleteDialogShow: false,
|
isDeleteDialogShow: false,
|
||||||
@ -165,6 +166,32 @@ class MylibRepoListItem extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onToggleMonitorRepo = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const repoName = this.props.repo.repo_name;
|
||||||
|
if (this.state.isMonitored) {
|
||||||
|
seafileAPI.unMonitorRepo(this.props.repo.repo_id).then(() => {
|
||||||
|
this.setState({isMonitored: !this.state.isMonitored});
|
||||||
|
const msg = gettext('Successfully unmonitored {library_name_placeholder}.')
|
||||||
|
.replace('{library_name_placeholder}', repoName);
|
||||||
|
toaster.success(msg);
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
seafileAPI.monitorRepo(this.props.repo.repo_id).then(() => {
|
||||||
|
this.setState({isMonitored: !this.state.isMonitored});
|
||||||
|
const msg = gettext('Successfully monitored {library_name_placeholder}.')
|
||||||
|
.replace('{library_name_placeholder}', repoName);
|
||||||
|
toaster.success(msg);
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onShareToggle = (e) => {
|
onShareToggle = (e) => {
|
||||||
// when close share dialog after send share link email,
|
// when close share dialog after send share link email,
|
||||||
// there is no event
|
// there is no event
|
||||||
@ -297,6 +324,13 @@ class MylibRepoListItem extends React.Component {
|
|||||||
<i className={`fa-star ${this.state.isStarred ? 'fas' : 'far star-empty'}`}></i>
|
<i className={`fa-star ${this.state.isStarred ? 'fas' : 'far star-empty'}`}></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
<td className="text-center">
|
||||||
|
<a href="#" role="button" aria-label={this.state.isMonitored ? gettext('unMonitor') : gettext('Monitor')} onClick={this.onToggleMonitorRepo}>
|
||||||
|
<i className={`fa-star ${this.state.isMonitored ? 'fas' : 'far star-empty'}`}></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
<td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
|
<td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
|
||||||
<td>
|
<td>
|
||||||
{this.state.isRenaming && (
|
{this.state.isRenaming && (
|
||||||
|
@ -85,8 +85,9 @@ class MylibRepoListView extends React.Component {
|
|||||||
<tr>
|
<tr>
|
||||||
<th width="4%"></th>
|
<th width="4%"></th>
|
||||||
<th width="4%"><span className="sr-only">{gettext('Library Type')}</span></th>
|
<th width="4%"><span className="sr-only">{gettext('Library Type')}</span></th>
|
||||||
|
<th width="4%"><span className="sr-only">{gettext('Library Type')}</span></th>
|
||||||
<th width={showStorageBackend ? '33%' : '38%'}><a className="d-block table-sort-op" href="#" onClick={this.sortByName}>{gettext('Name')} {this.props.sortBy === 'name' && sortIcon}</a></th>
|
<th width={showStorageBackend ? '33%' : '38%'}><a className="d-block table-sort-op" href="#" onClick={this.sortByName}>{gettext('Name')} {this.props.sortBy === 'name' && sortIcon}</a></th>
|
||||||
<th width="14%"><span className="sr-only">{gettext('Actions')}</span></th>
|
<th width="10%"><span className="sr-only">{gettext('Actions')}</span></th>
|
||||||
<th width={showStorageBackend ? '15%' : '20%'}><a className="d-block table-sort-op" href="#" onClick={this.sortBySize}>{gettext('Size')} {this.props.sortBy === 'size' && sortIcon}</a></th>
|
<th width={showStorageBackend ? '15%' : '20%'}><a className="d-block table-sort-op" href="#" onClick={this.sortBySize}>{gettext('Size')} {this.props.sortBy === 'size' && sortIcon}</a></th>
|
||||||
{showStorageBackend ? <th width="15%">{gettext('Storage Backend')}</th> : null}
|
{showStorageBackend ? <th width="15%">{gettext('Storage Backend')}</th> : null}
|
||||||
<th width={showStorageBackend ? '15%' : '20%'}><a className="d-block table-sort-op" href="#" onClick={this.sortByTime}>{gettext('Last Update')} {this.props.sortBy === 'time' && sortIcon}</a></th>
|
<th width={showStorageBackend ? '15%' : '20%'}><a className="d-block table-sort-op" href="#" onClick={this.sortByTime}>{gettext('Last Update')} {this.props.sortBy === 'time' && sortIcon}</a></th>
|
||||||
|
111
seahub/api2/endpoints/monitored_repos.py
Normal file
111
seahub/api2/endpoints/monitored_repos.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
# Copyright (c) 2012-2018 Seafile Ltd.
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from rest_framework.authentication import SessionAuthentication
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
|
from seaserv import seafile_api
|
||||||
|
|
||||||
|
from seahub.api2.utils import api_error
|
||||||
|
from seahub.api2.authentication import TokenAuthentication
|
||||||
|
from seahub.api2.throttling import UserRateThrottle
|
||||||
|
from seahub.utils.repo import is_repo_owner
|
||||||
|
|
||||||
|
from seahub.base.models import UserMonitoredRepos
|
||||||
|
from seahub.base.templatetags.seahub_tags import email2nickname, email2contact_email
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class MonitoredRepos(APIView):
|
||||||
|
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
permission_classes = (IsAuthenticated,)
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
""" Monitor a repo.
|
||||||
|
|
||||||
|
Permission checking:
|
||||||
|
1. Only repo owner can perform this action.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# argument check
|
||||||
|
repo_id = request.data.get('repo_id', None)
|
||||||
|
if not repo_id:
|
||||||
|
error_msg = 'repo_id invalid.'
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
# resource check
|
||||||
|
repo = seafile_api.get_repo(repo_id)
|
||||||
|
if not repo:
|
||||||
|
error_msg = 'Library %s not found.' % repo_id
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
|
# permission check
|
||||||
|
email = request.user.username
|
||||||
|
if not is_repo_owner(request, repo_id, email):
|
||||||
|
error_msg = 'Permission denied.'
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
|
# monitor a repo
|
||||||
|
monitored_repos = UserMonitoredRepos.objects.filter(email=email, repo_id=repo_id)
|
||||||
|
if monitored_repos:
|
||||||
|
error_msg = 'Library {} has been monitored.'.format(repo_id)
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
monitored_repo = UserMonitoredRepos.objects.create(email=email,
|
||||||
|
repo_id=repo_id)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
error_msg = 'Internal Server Error'
|
||||||
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||||
|
|
||||||
|
# get info of new monitored repo
|
||||||
|
item_info = {}
|
||||||
|
item_info['user_email'] = email
|
||||||
|
item_info['user_name'] = email2nickname(email)
|
||||||
|
item_info['user_contact_email'] = email2contact_email(email)
|
||||||
|
item_info['repo_id'] = monitored_repo.repo_id
|
||||||
|
|
||||||
|
return Response(item_info)
|
||||||
|
|
||||||
|
|
||||||
|
class MonitoredRepo(APIView):
|
||||||
|
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
permission_classes = (IsAuthenticated,)
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
|
||||||
|
def delete(self, request, repo_id):
|
||||||
|
""" Unmonitored repo.
|
||||||
|
|
||||||
|
Permission checking:
|
||||||
|
1. Only repo owner can perform this action.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# resource check
|
||||||
|
repo = seafile_api.get_repo(repo_id)
|
||||||
|
if not repo:
|
||||||
|
error_msg = 'Library %s not found.' % repo_id
|
||||||
|
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||||
|
|
||||||
|
# permission check
|
||||||
|
email = request.user.username
|
||||||
|
if not is_repo_owner(request, repo_id, email):
|
||||||
|
error_msg = 'Permission denied.'
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
|
# unmonitor repo
|
||||||
|
try:
|
||||||
|
UserMonitoredRepos.objects.filter(email=email, repo_id=repo_id).delete()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
error_msg = 'Internal Server Error'
|
||||||
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||||
|
|
||||||
|
return Response({'success': True})
|
@ -14,9 +14,8 @@ from seahub.api2.throttling import UserRateThrottle
|
|||||||
from seahub.notifications.models import UserNotification
|
from seahub.notifications.models import UserNotification
|
||||||
|
|
||||||
from seahub.notifications.models import get_cache_key_of_unseen_notifications
|
from seahub.notifications.models import get_cache_key_of_unseen_notifications
|
||||||
from seahub.notifications.views import add_notice_from_info
|
|
||||||
from seahub.notifications.utils import update_notice_detail
|
from seahub.notifications.utils import update_notice_detail
|
||||||
from seahub.api2.utils import api_error, to_python_boolean
|
from seahub.api2.utils import api_error
|
||||||
from seahub.utils.timeutils import datetime_to_isoformat_timestr
|
from seahub.utils.timeutils import datetime_to_isoformat_timestr
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -117,7 +116,6 @@ class NotificationsView(APIView):
|
|||||||
return Response({'success': True})
|
return Response({'success': True})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class NotificationView(APIView):
|
class NotificationView(APIView):
|
||||||
|
|
||||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
@ -14,7 +14,7 @@ from seahub.api2.utils import api_error
|
|||||||
|
|
||||||
from seahub.api2.endpoints.group_owned_libraries import get_group_id_by_repo_owner
|
from seahub.api2.endpoints.group_owned_libraries import get_group_id_by_repo_owner
|
||||||
|
|
||||||
from seahub.base.models import UserStarredFiles
|
from seahub.base.models import UserStarredFiles, UserMonitoredRepos
|
||||||
from seahub.base.templatetags.seahub_tags import email2nickname, \
|
from seahub.base.templatetags.seahub_tags import email2nickname, \
|
||||||
email2contact_email
|
email2contact_email
|
||||||
from seahub.signals import repo_deleted
|
from seahub.signals import repo_deleted
|
||||||
@ -79,6 +79,13 @@ class ReposView(APIView):
|
|||||||
logger.error(e)
|
logger.error(e)
|
||||||
starred_repo_id_list = []
|
starred_repo_id_list = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
monitored_repos = UserMonitoredRepos.objects.filter(email=email)
|
||||||
|
monitored_repo_id_list = [item.repo_id for item in monitored_repos]
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
monitored_repo_id_list = []
|
||||||
|
|
||||||
repo_info_list = []
|
repo_info_list = []
|
||||||
if filter_by['mine']:
|
if filter_by['mine']:
|
||||||
|
|
||||||
@ -120,6 +127,7 @@ class ReposView(APIView):
|
|||||||
"encrypted": r.encrypted,
|
"encrypted": r.encrypted,
|
||||||
"permission": 'rw', # Always have read-write permission to owned repo
|
"permission": 'rw', # Always have read-write permission to owned repo
|
||||||
"starred": r.repo_id in starred_repo_id_list,
|
"starred": r.repo_id in starred_repo_id_list,
|
||||||
|
"monitored": r.repo_id in monitored_repo_id_list,
|
||||||
"status": normalize_repo_status_code(r.status),
|
"status": normalize_repo_status_code(r.status),
|
||||||
"salt": r.salt if r.enc_version >= 3 else '',
|
"salt": r.salt if r.enc_version >= 3 else '',
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
# Copyright (c) 2012-2016 Seafile Ltd.
|
# Copyright (c) 2012-2016 Seafile Ltd.
|
||||||
import os
|
import os
|
||||||
import datetime
|
|
||||||
import logging
|
import logging
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
@ -10,7 +9,7 @@ from pysearpc import SearpcError
|
|||||||
from seaserv import seafile_api
|
from seaserv import seafile_api
|
||||||
|
|
||||||
from seahub.auth.signals import user_logged_in
|
from seahub.auth.signals import user_logged_in
|
||||||
from seahub.utils import calc_file_path_hash, within_time_range, \
|
from seahub.utils import within_time_range, \
|
||||||
normalize_file_path, normalize_dir_path
|
normalize_file_path, normalize_dir_path
|
||||||
from seahub.utils.timeutils import datetime_to_isoformat_timestr
|
from seahub.utils.timeutils import datetime_to_isoformat_timestr
|
||||||
from seahub.tags.models import FileUUIDMap
|
from seahub.tags.models import FileUUIDMap
|
||||||
@ -37,6 +36,7 @@ class TimestampedModel(models.Model):
|
|||||||
# default ordering for most models.
|
# default ordering for most models.
|
||||||
ordering = ['-created_at', '-updated_at']
|
ordering = ['-created_at', '-updated_at']
|
||||||
|
|
||||||
|
|
||||||
class FileCommentManager(models.Manager):
|
class FileCommentManager(models.Manager):
|
||||||
def add(self, repo_id, parent_path, item_name, author, comment, detail=''):
|
def add(self, repo_id, parent_path, item_name, author, comment, detail=''):
|
||||||
fileuuidmap = FileUUIDMap.objects.get_or_create_fileuuidmap(repo_id,
|
fileuuidmap = FileUUIDMap.objects.get_or_create_fileuuidmap(repo_id,
|
||||||
@ -67,7 +67,7 @@ class FileCommentManager(models.Manager):
|
|||||||
|
|
||||||
def get_by_parent_path(self, repo_id, parent_path):
|
def get_by_parent_path(self, repo_id, parent_path):
|
||||||
uuids = FileUUIDMap.objects.get_fileuuidmaps_by_parent_path(repo_id,
|
uuids = FileUUIDMap.objects.get_fileuuidmaps_by_parent_path(repo_id,
|
||||||
parent_path)
|
parent_path)
|
||||||
objs = super(FileCommentManager, self).filter(uuid__in=uuids)
|
objs = super(FileCommentManager, self).filter(uuid__in=uuids)
|
||||||
return objs
|
return objs
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ class FileComment(models.Model):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
########## starred files
|
# starred files
|
||||||
class StarredFile(object):
|
class StarredFile(object):
|
||||||
def format_path(self):
|
def format_path(self):
|
||||||
if self.path == "/":
|
if self.path == "/":
|
||||||
@ -129,6 +129,7 @@ class StarredFile(object):
|
|||||||
if not is_dir:
|
if not is_dir:
|
||||||
self.name = path.split('/')[-1]
|
self.name = path.split('/')[-1]
|
||||||
|
|
||||||
|
|
||||||
class UserStarredFilesManager(models.Manager):
|
class UserStarredFilesManager(models.Manager):
|
||||||
|
|
||||||
def get_starred_repos_by_user(self, email):
|
def get_starred_repos_by_user(self, email):
|
||||||
@ -139,23 +140,26 @@ class UserStarredFilesManager(models.Manager):
|
|||||||
def get_starred_item(self, email, repo_id, path):
|
def get_starred_item(self, email, repo_id, path):
|
||||||
|
|
||||||
path_list = [normalize_file_path(path), normalize_dir_path(path)]
|
path_list = [normalize_file_path(path), normalize_dir_path(path)]
|
||||||
starred_items = UserStarredFiles.objects.filter(email=email,
|
starred_items = UserStarredFiles.objects.filter(email=email, repo_id=repo_id) \
|
||||||
repo_id=repo_id).filter(Q(path__in=path_list))
|
.filter(Q(path__in=path_list))
|
||||||
|
|
||||||
return starred_items[0] if len(starred_items) > 0 else None
|
return starred_items[0] if len(starred_items) > 0 else None
|
||||||
|
|
||||||
def add_starred_item(self, email, repo_id, path, is_dir, org_id=-1):
|
def add_starred_item(self, email, repo_id, path, is_dir, org_id=-1):
|
||||||
|
|
||||||
starred_item = UserStarredFiles.objects.create(email=email,
|
starred_item = UserStarredFiles.objects.create(email=email,
|
||||||
repo_id=repo_id, path=path, is_dir=is_dir, org_id=org_id)
|
repo_id=repo_id,
|
||||||
|
path=path,
|
||||||
|
is_dir=is_dir,
|
||||||
|
org_id=org_id)
|
||||||
|
|
||||||
return starred_item
|
return starred_item
|
||||||
|
|
||||||
def delete_starred_item(self, email, repo_id, path):
|
def delete_starred_item(self, email, repo_id, path):
|
||||||
|
|
||||||
path_list = [normalize_file_path(path), normalize_dir_path(path)]
|
path_list = [normalize_file_path(path), normalize_dir_path(path)]
|
||||||
starred_items = UserStarredFiles.objects.filter(email=email,
|
starred_items = UserStarredFiles.objects.filter(email=email, repo_id=repo_id) \
|
||||||
repo_id=repo_id).filter(Q(path__in=path_list))
|
.filter(Q(path__in=path_list))
|
||||||
|
|
||||||
for item in starred_items:
|
for item in starred_items:
|
||||||
item.delete()
|
item.delete()
|
||||||
@ -201,8 +205,9 @@ class UserStarredFilesManager(models.Manager):
|
|||||||
sfile.delete()
|
sfile.delete()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# TODO: remove ``size`` from StarredFile
|
||||||
f = StarredFile(sfile.org_id, repo, file_id, sfile.path,
|
f = StarredFile(sfile.org_id, repo, file_id, sfile.path,
|
||||||
sfile.is_dir, 0) # TODO: remove ``size`` from StarredFile
|
sfile.is_dir, 0)
|
||||||
ret.append(f)
|
ret.append(f)
|
||||||
|
|
||||||
'''Calculate files last modification time'''
|
'''Calculate files last modification time'''
|
||||||
@ -227,6 +232,7 @@ class UserStarredFilesManager(models.Manager):
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
class UserStarredFiles(models.Model):
|
class UserStarredFiles(models.Model):
|
||||||
"""Starred files are marked by users to get quick access to it on user
|
"""Starred files are marked by users to get quick access to it on user
|
||||||
home page.
|
home page.
|
||||||
@ -240,7 +246,8 @@ class UserStarredFiles(models.Model):
|
|||||||
|
|
||||||
objects = UserStarredFilesManager()
|
objects = UserStarredFilesManager()
|
||||||
|
|
||||||
########## misc
|
|
||||||
|
# misc
|
||||||
class UserLastLoginManager(models.Manager):
|
class UserLastLoginManager(models.Manager):
|
||||||
def get_by_username(self, username):
|
def get_by_username(self, username):
|
||||||
"""Return last login record for a user, delete duplicates if there are
|
"""Return last login record for a user, delete duplicates if there are
|
||||||
@ -258,11 +265,13 @@ class UserLastLoginManager(models.Manager):
|
|||||||
logger.warn('Delete duplicate user last login record: %s' % username)
|
logger.warn('Delete duplicate user last login record: %s' % username)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
class UserLastLogin(models.Model):
|
class UserLastLogin(models.Model):
|
||||||
username = models.CharField(max_length=255, db_index=True)
|
username = models.CharField(max_length=255, db_index=True)
|
||||||
last_login = models.DateTimeField(default=timezone.now)
|
last_login = models.DateTimeField(default=timezone.now)
|
||||||
objects = UserLastLoginManager()
|
objects = UserLastLoginManager()
|
||||||
|
|
||||||
|
|
||||||
def update_last_login(sender, user, **kwargs):
|
def update_last_login(sender, user, **kwargs):
|
||||||
"""
|
"""
|
||||||
A signal receiver which updates the last_login date for
|
A signal receiver which updates the last_login date for
|
||||||
@ -273,14 +282,18 @@ def update_last_login(sender, user, **kwargs):
|
|||||||
user_last_login = UserLastLogin(username=user.username)
|
user_last_login = UserLastLogin(username=user.username)
|
||||||
user_last_login.last_login = timezone.now()
|
user_last_login.last_login = timezone.now()
|
||||||
user_last_login.save()
|
user_last_login.save()
|
||||||
|
|
||||||
|
|
||||||
user_logged_in.connect(update_last_login)
|
user_logged_in.connect(update_last_login)
|
||||||
|
|
||||||
|
|
||||||
class CommandsLastCheck(models.Model):
|
class CommandsLastCheck(models.Model):
|
||||||
"""Record last check time for Django/custom commands.
|
"""Record last check time for Django/custom commands.
|
||||||
"""
|
"""
|
||||||
command_type = models.CharField(max_length=100)
|
command_type = models.CharField(max_length=100)
|
||||||
last_check = models.DateTimeField()
|
last_check = models.DateTimeField()
|
||||||
|
|
||||||
|
|
||||||
class DeviceToken(models.Model):
|
class DeviceToken(models.Model):
|
||||||
"""
|
"""
|
||||||
The iOS device token model.
|
The iOS device token model.
|
||||||
@ -297,8 +310,10 @@ class DeviceToken(models.Model):
|
|||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return "/".join(self.user, self.token)
|
return "/".join(self.user, self.token)
|
||||||
|
|
||||||
|
|
||||||
_CLIENT_LOGIN_TOKEN_EXPIRATION_SECONDS = 30
|
_CLIENT_LOGIN_TOKEN_EXPIRATION_SECONDS = 30
|
||||||
|
|
||||||
|
|
||||||
class ClientLoginTokenManager(models.Manager):
|
class ClientLoginTokenManager(models.Manager):
|
||||||
def get_username(self, tokenstr):
|
def get_username(self, tokenstr):
|
||||||
try:
|
try:
|
||||||
@ -312,6 +327,7 @@ class ClientLoginTokenManager(models.Manager):
|
|||||||
return None
|
return None
|
||||||
return username
|
return username
|
||||||
|
|
||||||
|
|
||||||
class ClientLoginToken(models.Model):
|
class ClientLoginToken(models.Model):
|
||||||
# TODO: update sql/mysql.sql and sql/sqlite3.sql
|
# TODO: update sql/mysql.sql and sql/sqlite3.sql
|
||||||
token = models.CharField(max_length=32, primary_key=True)
|
token = models.CharField(max_length=32, primary_key=True)
|
||||||
@ -349,3 +365,11 @@ class RepoSecretKey(models.Model):
|
|||||||
secret_key = models.CharField(max_length=44)
|
secret_key = models.CharField(max_length=44)
|
||||||
|
|
||||||
objects = RepoSecretKeyManager()
|
objects = RepoSecretKeyManager()
|
||||||
|
|
||||||
|
|
||||||
|
class UserMonitoredRepos(models.Model):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
email = models.EmailField(db_index=True)
|
||||||
|
repo_id = models.CharField(max_length=36, db_index=True)
|
||||||
|
timestamp = models.DateTimeField(default=timezone.now)
|
||||||
|
@ -74,6 +74,7 @@ MSG_TYPE_DRAFT_COMMENT = 'draft_comment'
|
|||||||
MSG_TYPE_DRAFT_REVIEWER = 'draft_reviewer'
|
MSG_TYPE_DRAFT_REVIEWER = 'draft_reviewer'
|
||||||
MSG_TYPE_GUEST_INVITATION_ACCEPTED = 'guest_invitation_accepted'
|
MSG_TYPE_GUEST_INVITATION_ACCEPTED = 'guest_invitation_accepted'
|
||||||
MSG_TYPE_REPO_TRANSFER = 'repo_transfer'
|
MSG_TYPE_REPO_TRANSFER = 'repo_transfer'
|
||||||
|
MSG_TYPE_REPO_MINOTOR = 'repo_monitor'
|
||||||
|
|
||||||
USER_NOTIFICATION_COUNT_CACHE_PREFIX = 'USER_NOTIFICATION_COUNT_'
|
USER_NOTIFICATION_COUNT_CACHE_PREFIX = 'USER_NOTIFICATION_COUNT_'
|
||||||
|
|
||||||
@ -403,6 +404,9 @@ class UserNotification(models.Model):
|
|||||||
def is_repo_transfer_msg(self):
|
def is_repo_transfer_msg(self):
|
||||||
return self.msg_type == MSG_TYPE_REPO_TRANSFER
|
return self.msg_type == MSG_TYPE_REPO_TRANSFER
|
||||||
|
|
||||||
|
def is_repo_monitor_msg(self):
|
||||||
|
return self.msg_type == MSG_TYPE_REPO_MINOTOR
|
||||||
|
|
||||||
def user_message_detail_to_dict(self):
|
def user_message_detail_to_dict(self):
|
||||||
"""Parse user message detail, returns dict contains ``message`` and
|
"""Parse user message detail, returns dict contains ``message`` and
|
||||||
``msg_from``.
|
``msg_from``.
|
||||||
|
@ -3,7 +3,6 @@ import os
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.urls import reverse
|
|
||||||
|
|
||||||
from seaserv import ccnet_api, seafile_api
|
from seaserv import ccnet_api, seafile_api
|
||||||
from seahub.notifications.models import Notification
|
from seahub.notifications.models import Notification
|
||||||
@ -227,4 +226,25 @@ def update_notice_detail(request, notices):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
|
|
||||||
|
elif notice.is_repo_monitor_msg():
|
||||||
|
try:
|
||||||
|
d = json.loads(notice.detail)
|
||||||
|
|
||||||
|
repo_id = d['repo_id']
|
||||||
|
if repo_id in repo_dict:
|
||||||
|
repo = repo_dict[repo_id]
|
||||||
|
else:
|
||||||
|
repo = seafile_api.get_repo(repo_id)
|
||||||
|
repo_dict[repo_id] = repo
|
||||||
|
|
||||||
|
op_user_email = d.pop('op_user')
|
||||||
|
url, is_default, date_uploaded = api_avatar_url(op_user_email, 32)
|
||||||
|
d['op_user_avatar_url'] = url
|
||||||
|
d['op_user_email'] = op_user_email
|
||||||
|
d['op_user_name'] = email2nickname(op_user_email)
|
||||||
|
d['op_user_contact_email'] = email2contact_email(op_user_email)
|
||||||
|
notice.detail = d
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
|
||||||
return notices
|
return notices
|
||||||
|
@ -94,6 +94,7 @@ from seahub.api2.endpoints.tag_filter_file import TaggedFilesView
|
|||||||
from seahub.api2.endpoints.related_files import RelatedFilesView, RelatedFileView
|
from seahub.api2.endpoints.related_files import RelatedFilesView, RelatedFileView
|
||||||
from seahub.api2.endpoints.webdav_secret import WebdavSecretView
|
from seahub.api2.endpoints.webdav_secret import WebdavSecretView
|
||||||
from seahub.api2.endpoints.starred_items import StarredItems
|
from seahub.api2.endpoints.starred_items import StarredItems
|
||||||
|
from seahub.api2.endpoints.monitored_repos import MonitoredRepos, MonitoredRepo
|
||||||
from seahub.api2.endpoints.markdown_lint import MarkdownLintView
|
from seahub.api2.endpoints.markdown_lint import MarkdownLintView
|
||||||
from seahub.api2.endpoints.public_repos_search import PublishedRepoSearchView
|
from seahub.api2.endpoints.public_repos_search import PublishedRepoSearchView
|
||||||
from seahub.api2.endpoints.recent_added_files import RecentAddedFilesView
|
from seahub.api2.endpoints.recent_added_files import RecentAddedFilesView
|
||||||
@ -467,6 +468,10 @@ urlpatterns = [
|
|||||||
## user::starred-item
|
## user::starred-item
|
||||||
url(r'^api/v2.1/starred-items/$', StarredItems.as_view(), name='api-v2.1-starred-items'),
|
url(r'^api/v2.1/starred-items/$', StarredItems.as_view(), name='api-v2.1-starred-items'),
|
||||||
|
|
||||||
|
## user::monitored-repos
|
||||||
|
url(r'^api/v2.1/monitored-repos/$', MonitoredRepos.as_view(), name='api-v2.1-monitored-repos'),
|
||||||
|
url(r'^api/v2.1/monitored-repos/(?P<repo_id>[-0-9a-f]{36})/$', MonitoredRepo.as_view(), name='api-v2.1-monitored-repo'),
|
||||||
|
|
||||||
## user::wiki
|
## user::wiki
|
||||||
url(r'^api/v2.1/wikis/$', WikisView.as_view(), name='api-v2.1-wikis'),
|
url(r'^api/v2.1/wikis/$', WikisView.as_view(), name='api-v2.1-wikis'),
|
||||||
url(r'^api/v2.1/wikis/(?P<slug>[^/]+)/$', WikiView.as_view(), name='api-v2.1-wiki'),
|
url(r'^api/v2.1/wikis/(?P<slug>[^/]+)/$', WikiView.as_view(), name='api-v2.1-wiki'),
|
||||||
|
Loading…
Reference in New Issue
Block a user