mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-12 12:22:13 +00:00
invite user
This commit is contained in:
parent
175803baba
commit
f00b81f8d8
113
frontend/src/components/dialog/group-invite-members-dialog.js
Normal file
113
frontend/src/components/dialog/group-invite-members-dialog.js
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Button, Modal, ModalBody } from 'reactstrap';
|
||||||
|
import SeahubModalHeader from '@/components/common/seahub-modal-header';
|
||||||
|
import copy from 'copy-to-clipboard';
|
||||||
|
import toaster from '../toast';
|
||||||
|
import { gettext } from '../../utils/constants';
|
||||||
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
|
import { Utils } from '../../utils/utils';
|
||||||
|
|
||||||
|
import '../../css/group-invite-members-dialog.css';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
groupID: PropTypes.string.isRequired,
|
||||||
|
toggleGroupInviteDialog: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
class GroupInviteMembersDialog extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
inviteList: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.listInviteLinks();
|
||||||
|
}
|
||||||
|
|
||||||
|
listInviteLinks = () => {
|
||||||
|
seafileAPI.getGroupInviteLinks(this.props.groupID).then((res) => {
|
||||||
|
this.setState({ inviteList: res.data.group_invite_link_list });
|
||||||
|
}).catch(error => {
|
||||||
|
this.onError(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
addInviteLink = () => {
|
||||||
|
seafileAPI.addGroupInviteLinks(this.props.groupID).then(() => {
|
||||||
|
this.listInviteLinks();
|
||||||
|
}).catch(error => {
|
||||||
|
this.onError(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
deleteLink = (token) => {
|
||||||
|
seafileAPI.deleteGroupInviteLinks(this.props.groupID, token).then(() => {
|
||||||
|
this.listInviteLinks();
|
||||||
|
}).catch(error => {
|
||||||
|
this.onError(error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onError = (error) => {
|
||||||
|
let errMsg = Utils.getErrorMsg(error, true);
|
||||||
|
if (!error.response || error.response.status !== 403) {
|
||||||
|
toaster.danger(errMsg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
copyLink = () => {
|
||||||
|
const inviteLinkItem = this.state.inviteList[0];
|
||||||
|
copy(inviteLinkItem.link);
|
||||||
|
const message = gettext('Invitation link has been copied to clipboard');
|
||||||
|
toaster.success((message), {
|
||||||
|
duration: 2
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
toggle = () => {
|
||||||
|
this.props.toggleGroupInviteDialog();
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { inviteList } = this.state;
|
||||||
|
const link = inviteList[0];
|
||||||
|
return (
|
||||||
|
<Modal isOpen={true} toggle={this.toggle} className="group-invite-members">
|
||||||
|
<SeahubModalHeader toggle={this.toggle}>{gettext('Invite members')}</SeahubModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
{link ?
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<strong>{gettext('Group invitation link')}</strong>
|
||||||
|
</div>
|
||||||
|
<div className="invite-link-item">
|
||||||
|
<div className="form-item text-truncate">{link.link}</div>
|
||||||
|
<div className="invite-link-copy">
|
||||||
|
<Button color="primary" onClick={this.copyLink} className="invite-link-copy-btn text-truncate">{gettext('Copy')}</Button>
|
||||||
|
</div>
|
||||||
|
<Button color="secondary" onClick={this.deleteLink.bind(this, link.token)} className="delete-link-btn ml-2">
|
||||||
|
<i className="sf2-icon-delete"></i>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
:
|
||||||
|
<>
|
||||||
|
<div className="no-link-tip mb-4">
|
||||||
|
{gettext('No group invitation link yet. Group invitation link let registered users to join the group by clicking a link.')}
|
||||||
|
</div>
|
||||||
|
<Button color="primary" onClick={this.addInviteLink} className="my-4">{gettext('Generate')}</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</ModalBody>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupInviteMembersDialog.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default GroupInviteMembersDialog;
|
40
frontend/src/css/group-invite-members-dialog.css
Normal file
40
frontend/src/css/group-invite-members-dialog.css
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
.group-invite-members th,
|
||||||
|
.group-invite-members td {
|
||||||
|
vertical-align: middle;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-invite-members .no-link-tip {
|
||||||
|
line-height: 24px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-link-item {
|
||||||
|
display: flex;
|
||||||
|
margin: 1rem 0 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-link-item .form-item {
|
||||||
|
width: calc(100% - 120px);
|
||||||
|
padding-left: 10px;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-link-item .invite-link-copy {
|
||||||
|
width: 72px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-link-item .invite-link-copy-btn {
|
||||||
|
width: 72px;
|
||||||
|
height: 40px;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-link-item .delete-link-btn {
|
||||||
|
color: #999;
|
||||||
|
width: 40px;
|
||||||
|
}
|
@ -2,7 +2,7 @@ import React, { Fragment } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import cookie from 'react-cookies';
|
import cookie from 'react-cookies';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { gettext, username, canAddRepo } from '../../utils/constants';
|
import { gettext, username, canAddRepo, isMultiTenancy } from '../../utils/constants';
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import Loading from '../../components/loading';
|
import Loading from '../../components/loading';
|
||||||
@ -21,6 +21,7 @@ import DepartmentDetailDialog from '../../components/dialog/department-detail-di
|
|||||||
import LeaveGroupDialog from '../../components/dialog/leave-group-dialog';
|
import LeaveGroupDialog from '../../components/dialog/leave-group-dialog';
|
||||||
import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
|
import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
|
||||||
import SortOptionsDialog from '../../components/dialog/sort-options';
|
import SortOptionsDialog from '../../components/dialog/sort-options';
|
||||||
|
import GroupInviteMembersDialog from '../../components/dialog/group-invite-members-dialog';
|
||||||
import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar';
|
import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar';
|
||||||
import ViewModes from '../../components/view-modes';
|
import ViewModes from '../../components/view-modes';
|
||||||
import ReposSortMenu from '../../components/sort-menu';
|
import ReposSortMenu from '../../components/sort-menu';
|
||||||
@ -65,6 +66,7 @@ class GroupView extends React.Component {
|
|||||||
showTransferGroupDialog: false,
|
showTransferGroupDialog: false,
|
||||||
showImportMembersDialog: false,
|
showImportMembersDialog: false,
|
||||||
showManageMembersDialog: false,
|
showManageMembersDialog: false,
|
||||||
|
showInviteMembersDialog: false,
|
||||||
isLeaveGroupDialogOpen: false,
|
isLeaveGroupDialogOpen: false,
|
||||||
isMembersDialogOpen: false
|
isMembersDialogOpen: false
|
||||||
};
|
};
|
||||||
@ -287,6 +289,13 @@ class GroupView extends React.Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
toggleInviteMembersDialog = () => {
|
||||||
|
this.setState({
|
||||||
|
showInviteMembersDialog: !this.state.showInviteMembersDialog,
|
||||||
|
showGroupDropdown: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
importMembersInBatch = (file) => {
|
importMembersInBatch = (file) => {
|
||||||
toaster.notify(gettext('It may take some time, please wait.'));
|
toaster.notify(gettext('It may take some time, please wait.'));
|
||||||
seafileAPI.importGroupMembersViaFile(this.state.currentGroup.id, file).then((res) => {
|
seafileAPI.importGroupMembersViaFile(this.state.currentGroup.id, file).then((res) => {
|
||||||
@ -365,6 +374,7 @@ class GroupView extends React.Component {
|
|||||||
|
|
||||||
getOpList = () => {
|
getOpList = () => {
|
||||||
const { currentGroup, isDepartmentGroup, isStaff, isOwner } = this.state;
|
const { currentGroup, isDepartmentGroup, isStaff, isOwner } = this.state;
|
||||||
|
// const isGroup = this.state.currentGroup.owner !== 'system admin';
|
||||||
const opList = [];
|
const opList = [];
|
||||||
if ((!isDepartmentGroup && canAddRepo) ||
|
if ((!isDepartmentGroup && canAddRepo) ||
|
||||||
(isDepartmentGroup && isStaff)) {
|
(isDepartmentGroup && isStaff)) {
|
||||||
@ -389,6 +399,10 @@ class GroupView extends React.Component {
|
|||||||
if (!isOwner && !isDepartmentGroup) {
|
if (!isOwner && !isDepartmentGroup) {
|
||||||
opList.push({ 'text': gettext('Leave group'), 'onClick': this.toggleLeaveGroupDialog });
|
opList.push({ 'text': gettext('Leave group'), 'onClick': this.toggleLeaveGroupDialog });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isOwner && this.state.currentGroup.owner !== 'system admin' && !isMultiTenancy) {
|
||||||
|
opList.push({ 'text': gettext('Invite Members'), 'onClick': this.toggleInviteMembersDialog });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return opList;
|
return opList;
|
||||||
@ -589,6 +603,12 @@ class GroupView extends React.Component {
|
|||||||
onGroupChanged={this.props.onGroupChanged}
|
onGroupChanged={this.props.onGroupChanged}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
{this.state.showInviteMembersDialog &&
|
||||||
|
<GroupInviteMembersDialog
|
||||||
|
groupID={this.props.groupID}
|
||||||
|
onGroupChanged={this.props.onGroupChanged}
|
||||||
|
toggleGroupInviteDialog={this.toggleInviteMembersDialog}/>
|
||||||
|
}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -112,6 +112,7 @@ export const enablePDFThumbnail = window.app.pageOptions.enablePDFThumbnail;
|
|||||||
export const enableOnlyoffice = window.app.pageOptions.enableOnlyoffice || false;
|
export const enableOnlyoffice = window.app.pageOptions.enableOnlyoffice || false;
|
||||||
export const onlyofficeConverterExtensions = window.app.pageOptions.onlyofficeConverterExtensions || [];
|
export const onlyofficeConverterExtensions = window.app.pageOptions.onlyofficeConverterExtensions || [];
|
||||||
|
|
||||||
|
export const isMultiTenancy = window.app.pageOptions.isMultiTenacy;
|
||||||
export const enableFileTags = window.app.pageOptions.enableFileTags || false;
|
export const enableFileTags = window.app.pageOptions.enableFileTags || false;
|
||||||
|
|
||||||
export const enableShowAbout = window.app.pageOptions.enableShowAbout || false;
|
export const enableShowAbout = window.app.pageOptions.enableShowAbout || false;
|
||||||
|
@ -303,6 +303,22 @@ class SeafileAPI {
|
|||||||
return this.req.delete(url, { data: params });
|
return this.req.delete(url, { data: params });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteGroupInviteLinks(groupID, token) {
|
||||||
|
const url = this.server + '/api/v2.1/groups/' + groupID + '/invite-links/' + token + '/';
|
||||||
|
return this.req.delete(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
addGroupInviteLinks(groupID) {
|
||||||
|
const url = this.server + '/api/v2.1/groups/' + groupID + '/invite-links/';
|
||||||
|
let formData = new FormData();
|
||||||
|
return this._sendPostRequest(url, formData);
|
||||||
|
}
|
||||||
|
|
||||||
|
getGroupInviteLinks(groupID) {
|
||||||
|
const url = this.server + '/api/v2.1/groups/' + groupID + '/invite-links/';
|
||||||
|
return this.req.get(url);
|
||||||
|
}
|
||||||
|
|
||||||
// ---- share operation
|
// ---- share operation
|
||||||
|
|
||||||
listShareLinks({ repoID, path, page, perPage }) {
|
listShareLinks({ repoID, path, page, perPage }) {
|
||||||
|
@ -3,7 +3,7 @@ import logging
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from openpyxl import load_workbook
|
from openpyxl import load_workbook
|
||||||
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse, HttpResponseRedirect
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from rest_framework.authentication import SessionAuthentication
|
from rest_framework.authentication import SessionAuthentication
|
||||||
@ -19,16 +19,18 @@ from seahub.api2.utils import api_error
|
|||||||
from seahub.api2.endpoints.utils import is_org_user
|
from seahub.api2.endpoints.utils import is_org_user
|
||||||
from seahub.api2.throttling import UserRateThrottle
|
from seahub.api2.throttling import UserRateThrottle
|
||||||
from seahub.api2.authentication import TokenAuthentication
|
from seahub.api2.authentication import TokenAuthentication
|
||||||
from seahub.avatar.settings import AVATAR_DEFAULT_SIZE
|
|
||||||
from seahub.base.templatetags.seahub_tags import email2nickname
|
from seahub.base.templatetags.seahub_tags import email2nickname
|
||||||
from seahub.utils import string2list, is_org_context, get_file_type_and_ext
|
from seahub.utils import string2list, is_org_context, get_file_type_and_ext
|
||||||
|
from seahub.group.models import GroupInviteLinkModel
|
||||||
from seahub.utils.ms_excel import write_xls
|
from seahub.utils.ms_excel import write_xls
|
||||||
from seahub.utils.error_msg import file_type_error_msg
|
from seahub.utils.error_msg import file_type_error_msg
|
||||||
from seahub.base.accounts import User
|
from seahub.base.accounts import User
|
||||||
from seahub.group.signals import add_user_to_group
|
from seahub.group.signals import add_user_to_group
|
||||||
|
from seahub.group.views import group_invite
|
||||||
from seahub.group.utils import is_group_member, is_group_admin, \
|
from seahub.group.utils import is_group_member, is_group_admin, \
|
||||||
is_group_owner, is_group_admin_or_owner, get_group_member_info
|
is_group_owner, is_group_admin_or_owner, get_group_member_info
|
||||||
from seahub.profile.models import Profile
|
from seahub.profile.models import Profile
|
||||||
|
from seahub.settings import MULTI_TENANCY
|
||||||
|
|
||||||
from .utils import api_check_group
|
from .utils import api_check_group
|
||||||
|
|
||||||
@ -541,3 +543,96 @@ class GroupMembersImportExample(APIView):
|
|||||||
wb.save(response)
|
wb.save(response)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class GroupInviteLinks(APIView):
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
permission_classes = (IsAuthenticated,)
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
|
||||||
|
@api_check_group
|
||||||
|
def get(self, request, group_id):
|
||||||
|
"""
|
||||||
|
Get invitation link
|
||||||
|
"""
|
||||||
|
group_id = int(group_id)
|
||||||
|
email = request.user.username
|
||||||
|
|
||||||
|
if MULTI_TENANCY:
|
||||||
|
error_msg = 'Feature disabled.'
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
group = ccnet_api.get_group(group_id)
|
||||||
|
if group.creator_name == "system admin":
|
||||||
|
error_msg = 'Forbidden to operate department group'
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
if not is_group_admin_or_owner(group_id, email):
|
||||||
|
error_msg = 'Permission denied.'
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
invite_link_query_set = GroupInviteLinkModel.objects.filter(group_id=group_id)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f'query group invite links failed. {e}')
|
||||||
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||||
|
|
||||||
|
return Response({'group_invite_link_list': [group_invite_link.to_dict() for group_invite_link in
|
||||||
|
invite_link_query_set]})
|
||||||
|
|
||||||
|
@api_check_group
|
||||||
|
def post(self, request, group_id):
|
||||||
|
group_id = int(group_id)
|
||||||
|
email = request.user.username
|
||||||
|
if MULTI_TENANCY:
|
||||||
|
error_msg = 'Feature disabled.'
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
group = ccnet_api.get_group(group_id)
|
||||||
|
if group.creator_name == "system admin":
|
||||||
|
error_msg = 'Forbidden to operate department group'
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
if not is_group_admin_or_owner(group_id, email):
|
||||||
|
error_msg = 'Permission denied.'
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
invite_link = GroupInviteLinkModel.objects.create_link(group_id, email)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f'create group invite links failed. {e}')
|
||||||
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||||
|
|
||||||
|
return Response(invite_link.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
class GroupInviteLink(APIView):
|
||||||
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
|
permission_classes = (IsAuthenticated,)
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
|
||||||
|
@api_check_group
|
||||||
|
def delete(self, request, group_id, token):
|
||||||
|
group_id = int(group_id)
|
||||||
|
email = request.user.username
|
||||||
|
|
||||||
|
if MULTI_TENANCY:
|
||||||
|
error_msg = 'Feature disabled.'
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
group = ccnet_api.get_group(group_id)
|
||||||
|
if group.creator_name == "system admin":
|
||||||
|
error_msg = 'Forbidden to operate department group'
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
|
if not is_group_admin_or_owner(group_id, email):
|
||||||
|
error_msg = 'Permission denied.'
|
||||||
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
GroupInviteLinkModel.objects.filter(token=token, group_id=group_id).delete()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f'delete group invite links failed. {e}')
|
||||||
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||||
|
|
||||||
|
return Response({'success': True})
|
41
seahub/group/models.py
Normal file
41
seahub/group/models.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import logging
|
||||||
|
import uuid
|
||||||
|
from django.db import models
|
||||||
|
from seahub.settings import SERVICE_URL
|
||||||
|
from seahub.utils.timeutils import datetime_to_isoformat_timestr
|
||||||
|
from seahub.base.templatetags.seahub_tags import email2nickname
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class GroupInviteLinkModelManager(models.Manager):
|
||||||
|
def create_link(self, group_id, email):
|
||||||
|
token = uuid.uuid4().hex[:8]
|
||||||
|
while self.model.objects.filter(token=token).exists():
|
||||||
|
token = uuid.uuid4().hex[:8]
|
||||||
|
|
||||||
|
group_invite_link = super(GroupInviteLinkModelManager, self).create(
|
||||||
|
group_id=group_id, token=token, created_by=email)
|
||||||
|
return group_invite_link
|
||||||
|
|
||||||
|
|
||||||
|
class GroupInviteLinkModel(models.Model):
|
||||||
|
token = models.CharField(max_length=40, db_index=True)
|
||||||
|
group_id = models.IntegerField(db_index=True, null=False)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
created_by = models.CharField(max_length=255)
|
||||||
|
|
||||||
|
objects = GroupInviteLinkModelManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = 'group_invite_link'
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
result = {
|
||||||
|
'id': self.pk,
|
||||||
|
'token': self.token,
|
||||||
|
'group_id': self.group_id,
|
||||||
|
'created_at': datetime_to_isoformat_timestr(self.created_at),
|
||||||
|
'created_by': email2nickname(self.created_by),
|
||||||
|
'link': f"{SERVICE_URL.rstrip('/')}/group-invite/{self.token}/",
|
||||||
|
}
|
||||||
|
return result
|
@ -18,9 +18,10 @@ from seahub.auth import REDIRECT_FIELD_NAME
|
|||||||
from seahub.base.decorators import sys_staff_required, require_POST
|
from seahub.base.decorators import sys_staff_required, require_POST
|
||||||
from seahub.group.utils import validate_group_name, BadGroupNameError, \
|
from seahub.group.utils import validate_group_name, BadGroupNameError, \
|
||||||
ConflictGroupNameError, is_group_member
|
ConflictGroupNameError, is_group_member
|
||||||
from seahub.settings import SITE_ROOT
|
from seahub.group.models import GroupInviteLinkModel
|
||||||
|
from seahub.settings import SITE_ROOT, SERVICE_URL, MULTI_TENANCY
|
||||||
from seahub.utils import send_html_email, is_org_context, \
|
from seahub.utils import send_html_email, is_org_context, \
|
||||||
get_site_name
|
get_site_name, render_error
|
||||||
from seahub.share.models import ExtraGroupsSharePermission
|
from seahub.share.models import ExtraGroupsSharePermission
|
||||||
|
|
||||||
|
|
||||||
@ -170,3 +171,33 @@ def send_group_member_add_mail(request, group, from_user, to_user):
|
|||||||
|
|
||||||
subject = _('You are invited to join a group on %s') % get_site_name()
|
subject = _('You are invited to join a group on %s') % get_site_name()
|
||||||
send_html_email(subject, 'group/add_member_email.html', c, None, [to_user])
|
send_html_email(subject, 'group/add_member_email.html', c, None, [to_user])
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def group_invite(request, token):
|
||||||
|
"""
|
||||||
|
registered user add to group
|
||||||
|
"""
|
||||||
|
if MULTI_TENANCY:
|
||||||
|
return render_error(request, _('Feature disabled.'))
|
||||||
|
|
||||||
|
email = request.user.username
|
||||||
|
next_url = request.GET.get('next', '/')
|
||||||
|
redirect_to = SERVICE_URL.rstrip('/') + '/' + next_url.lstrip('/')
|
||||||
|
group_invite_link = GroupInviteLinkModel.objects.filter(token=token).first()
|
||||||
|
if not group_invite_link:
|
||||||
|
return render_error(request, _('Group invite link does not exist'))
|
||||||
|
|
||||||
|
if is_group_member(group_invite_link.group_id, email):
|
||||||
|
|
||||||
|
return HttpResponseRedirect(redirect_to)
|
||||||
|
|
||||||
|
if not group_invite_link.created_by:
|
||||||
|
return render_error(request, _('Group invite link broken'))
|
||||||
|
|
||||||
|
try:
|
||||||
|
ccnet_api.group_add_member(group_invite_link.group_id, group_invite_link.created_by, email)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f'group invite add user failed. {e}')
|
||||||
|
return render_error(request, 'Internal Server Error')
|
||||||
|
|
||||||
|
return HttpResponseRedirect(redirect_to)
|
||||||
|
@ -158,6 +158,7 @@
|
|||||||
enableWhiteboard: {% if enable_whiteboard %} true {% else %} false {% endif %},
|
enableWhiteboard: {% if enable_whiteboard %} true {% else %} false {% endif %},
|
||||||
isOrgContext: {% if org is not None %} true {% else %} false {% endif %},
|
isOrgContext: {% if org is not None %} true {% else %} false {% endif %},
|
||||||
enableMetadataManagement: {% if enable_metadata_management %} true {% else %} false {% endif %},
|
enableMetadataManagement: {% if enable_metadata_management %} true {% else %} false {% endif %},
|
||||||
|
isMultiTenacy: {% if multi_tenancy %} true {% else %} false {% endif %},
|
||||||
enableFileTags: {% if enable_file_tags %} true {% else %} false {% endif %},
|
enableFileTags: {% if enable_file_tags %} true {% else %} false {% endif %},
|
||||||
enableShowAbout: {% if enable_show_about %} true {% else %} false {% endif %},
|
enableShowAbout: {% if enable_show_about %} true {% else %} false {% endif %},
|
||||||
enableMultipleOfficeSuite: {% if user.permissions.can_choose_office_suite %} true {% else %} false {% endif %},
|
enableMultipleOfficeSuite: {% if user.permissions.can_choose_office_suite %} true {% else %} false {% endif %},
|
||||||
|
@ -45,7 +45,8 @@ from seahub.api2.endpoints.address_book.groups import AddressBookGroupsSubGroups
|
|||||||
from seahub.api2.endpoints.address_book.members import AddressBookGroupsSearchMember
|
from seahub.api2.endpoints.address_book.members import AddressBookGroupsSearchMember
|
||||||
|
|
||||||
from seahub.api2.endpoints.group_members import GroupMembers, GroupSearchMember, GroupMember, \
|
from seahub.api2.endpoints.group_members import GroupMembers, GroupSearchMember, GroupMember, \
|
||||||
GroupMembersBulk, GroupMembersImport, GroupMembersImportExample
|
GroupMembersBulk, GroupMembersImport, GroupMembersImportExample, GroupInviteLinks, GroupInviteLink, \
|
||||||
|
group_invite
|
||||||
from seahub.api2.endpoints.search_group import SearchGroup
|
from seahub.api2.endpoints.search_group import SearchGroup
|
||||||
from seahub.api2.endpoints.share_links import ShareLinks, ShareLink, \
|
from seahub.api2.endpoints.share_links import ShareLinks, ShareLink, \
|
||||||
ShareLinkOnlineOfficeLock, ShareLinkDirents, ShareLinkSaveFileToRepo, \
|
ShareLinkOnlineOfficeLock, ShareLinkDirents, ShareLinkSaveFileToRepo, \
|
||||||
@ -301,6 +302,7 @@ urlpatterns = [
|
|||||||
path('my-libs/', react_fake_view, name="my_libs"),
|
path('my-libs/', react_fake_view, name="my_libs"),
|
||||||
path('groups/', react_fake_view, name="groups"),
|
path('groups/', react_fake_view, name="groups"),
|
||||||
path('group/<int:group_id>/', react_fake_view, name="group"),
|
path('group/<int:group_id>/', react_fake_view, name="group"),
|
||||||
|
re_path(r'^group-invite/(?P<token>[-0-9a-f]{8})/$', group_invite, name='group_invite'),
|
||||||
re_path(r'^library/(?P<repo_id>[-0-9a-f]{36})/$', react_fake_view, name="library_view"),
|
re_path(r'^library/(?P<repo_id>[-0-9a-f]{36})/$', react_fake_view, name="library_view"),
|
||||||
re_path(r'^library/(?P<repo_id>[-0-9a-f]{36})/(?P<repo_name>[^/]+)/(?P<path>.*)$', react_fake_view, name="lib_view"),
|
re_path(r'^library/(?P<repo_id>[-0-9a-f]{36})/(?P<repo_name>[^/]+)/(?P<path>.*)$', react_fake_view, name="lib_view"),
|
||||||
re_path(r'^remote-library/(?P<provider_id>[-0-9a-f]{36})/(?P<repo_id>[-0-9a-f]{36})/(?P<repo_name>[^/]+)/(?P<path>.*)$', react_fake_view, name="remote_lib_view"),
|
re_path(r'^remote-library/(?P<provider_id>[-0-9a-f]{36})/(?P<repo_id>[-0-9a-f]{36})/(?P<repo_name>[^/]+)/(?P<path>.*)$', react_fake_view, name="remote_lib_view"),
|
||||||
@ -367,7 +369,8 @@ urlpatterns = [
|
|||||||
re_path(r'^api/v2.1/group-members-import-example/$', GroupMembersImportExample.as_view(), name='api-v2.1-group-members-import-example'),
|
re_path(r'^api/v2.1/group-members-import-example/$', GroupMembersImportExample.as_view(), name='api-v2.1-group-members-import-example'),
|
||||||
re_path(r'^api/v2.1/groups/(?P<group_id>\d+)/members/(?P<email>[^/]+)/$', GroupMember.as_view(), name='api-v2.1-group-member'),
|
re_path(r'^api/v2.1/groups/(?P<group_id>\d+)/members/(?P<email>[^/]+)/$', GroupMember.as_view(), name='api-v2.1-group-member'),
|
||||||
re_path(r'^api/v2.1/search-group/$', SearchGroup.as_view(), name='api-v2.1-search-group'),
|
re_path(r'^api/v2.1/search-group/$', SearchGroup.as_view(), name='api-v2.1-search-group'),
|
||||||
|
re_path(r'^api/v2.1/groups/(?P<group_id>\d+)/invite-links/$', GroupInviteLinks.as_view(),name='api-v2.1-group-invite-links'),
|
||||||
|
re_path(r'^api/v2.1/groups/(?P<group_id>\d+)/invite-links/(?P<token>[-0-9a-f]{8})/$', GroupInviteLink.as_view(), name='api-v2.1-group-invite-link'),
|
||||||
## address book
|
## address book
|
||||||
re_path(r'^api/v2.1/address-book/groups/(?P<group_id>\d+)/sub-groups/$', AddressBookGroupsSubGroups.as_view(), name='api-v2.1-address-book-groups-sub-groups'),
|
re_path(r'^api/v2.1/address-book/groups/(?P<group_id>\d+)/sub-groups/$', AddressBookGroupsSubGroups.as_view(), name='api-v2.1-address-book-groups-sub-groups'),
|
||||||
re_path(r'^api/v2.1/address-book/groups/(?P<group_id>\d+)/search-member/$', AddressBookGroupsSearchMember.as_view(), name='api-v2.1-address-book-search-member'),
|
re_path(r'^api/v2.1/address-book/groups/(?P<group_id>\d+)/search-member/$', AddressBookGroupsSearchMember.as_view(), name='api-v2.1-address-book-search-member'),
|
||||||
|
@ -58,7 +58,7 @@ from seahub.settings import AVATAR_FILE_STORAGE, ENABLE_REPO_SNAPSHOT_LABEL, \
|
|||||||
UPLOAD_LINK_EXPIRE_DAYS_MIN, UPLOAD_LINK_EXPIRE_DAYS_MAX, UPLOAD_LINK_EXPIRE_DAYS_DEFAULT, \
|
UPLOAD_LINK_EXPIRE_DAYS_MIN, UPLOAD_LINK_EXPIRE_DAYS_MAX, UPLOAD_LINK_EXPIRE_DAYS_DEFAULT, \
|
||||||
ENABLE_RESET_ENCRYPTED_REPO_PASSWORD, \
|
ENABLE_RESET_ENCRYPTED_REPO_PASSWORD, \
|
||||||
ADDITIONAL_SHARE_DIALOG_NOTE, ADDITIONAL_ABOUT_DIALOG_LINKS, \
|
ADDITIONAL_SHARE_DIALOG_NOTE, ADDITIONAL_ABOUT_DIALOG_LINKS, \
|
||||||
SEADOC_SERVER_URL, SHOW_WECHAT_SUPPORT_GROUP
|
SEADOC_SERVER_URL, SHOW_WECHAT_SUPPORT_GROUP, MULTI_TENANCY
|
||||||
|
|
||||||
from seahub.ocm.settings import ENABLE_OCM, OCM_REMOTE_SERVERS
|
from seahub.ocm.settings import ENABLE_OCM, OCM_REMOTE_SERVERS
|
||||||
from seahub.ocm_via_webdav.settings import ENABLE_OCM_VIA_WEBDAV
|
from seahub.ocm_via_webdav.settings import ENABLE_OCM_VIA_WEBDAV
|
||||||
@ -1148,7 +1148,8 @@ def react_fake_view(request, **kwargs):
|
|||||||
'enable_sso_to_thirdpart_website': settings.ENABLE_SSO_TO_THIRDPART_WEBSITE,
|
'enable_sso_to_thirdpart_website': settings.ENABLE_SSO_TO_THIRDPART_WEBSITE,
|
||||||
'enable_metadata_management': ENABLE_METADATA_MANAGEMENT,
|
'enable_metadata_management': ENABLE_METADATA_MANAGEMENT,
|
||||||
'enable_file_tags': settings.ENABLE_FILE_TAGS,
|
'enable_file_tags': settings.ENABLE_FILE_TAGS,
|
||||||
'enable_show_about': settings.ENABLE_SHOW_ABOUT
|
'enable_show_about': settings.ENABLE_SHOW_ABOUT,
|
||||||
|
'multi_tenancy': MULTI_TENANCY,
|
||||||
}
|
}
|
||||||
|
|
||||||
if ENABLE_METADATA_MANAGEMENT:
|
if ENABLE_METADATA_MANAGEMENT:
|
||||||
|
@ -1587,4 +1587,15 @@ CREATE TABLE `RepoTransfer` (
|
|||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `idx_file_transfer_org_id` (`org_id`),
|
KEY `idx_file_transfer_org_id` (`org_id`),
|
||||||
KEY `idx_file_transfer_timestamp` (`timestamp`)
|
KEY `idx_file_transfer_timestamp` (`timestamp`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
CREATE TABLE `group_invite_link` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`token` varchar(40) NOT NULL,
|
||||||
|
`group_id` int(11) NOT NULL,
|
||||||
|
`created_at` datetime(6) NOT NULL,
|
||||||
|
`created_by` varchar(255) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `group_invite_link_group_id` (`group_id`),
|
||||||
|
KEY `group_invite_link_token` (`token`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
Loading…
Reference in New Issue
Block a user