From 5abb686efb05d52bcdd51e8f5e979b9b2f763bfd Mon Sep 17 00:00:00 2001 From: skywalker Date: Wed, 28 Jun 2023 14:48:59 +0800 Subject: [PATCH] sdocStartRevise --- .../dirent-grid-view/dirent-grid-view.js | 43 +++++++ .../dirent-list-view/dirent-list-item.js | 15 +++ .../toolbar/multiple-dir-operation-toolbar.js | 43 +++++++ frontend/src/utils/text-translation.js | 1 + frontend/src/utils/utils.js | 3 +- seahub/seadoc/apis.py | 106 ++++++++++++------ seahub/seadoc/models.py | 47 +++++--- seahub/seadoc/sdoc_server_api.py | 38 +++++++ seahub/seadoc/urls.py | 4 +- sql/mysql.sql | 4 +- 10 files changed, 250 insertions(+), 54 deletions(-) create mode 100644 seahub/seadoc/sdoc_server_api.py diff --git a/frontend/src/components/dirent-grid-view/dirent-grid-view.js b/frontend/src/components/dirent-grid-view/dirent-grid-view.js index 34167a6a6d..e17319f950 100644 --- a/frontend/src/components/dirent-grid-view/dirent-grid-view.js +++ b/frontend/src/components/dirent-grid-view/dirent-grid-view.js @@ -150,6 +150,15 @@ class DirentGridView extends React.Component{ case 'Lock': this.onLockItem(currentObject); break; + case 'Mask as draft': + this.onMaskAsDraft(currentObject); + break; + case 'Unmask as draft': + this.onUnmaskAsDraft(currentObject); + break; + case 'Start revise': + this.onStartRevise(currentObject); + break; case 'Comment': this.onCommentItem(); break; @@ -262,6 +271,40 @@ class DirentGridView extends React.Component{ }); } + onMaskAsDraft = (currentObject) => { + let repoID = this.props.repoID; + let filePath = this.getDirentPath(currentObject); + seafileAPI.sdocMaskAsDraft(repoID, filePath).then((res) => { + this.props.updateDirent(currentObject, 'is_sdoc_draft', true); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + + onUnmaskAsDraft = (currentObject) => { + let repoID = this.props.repoID; + let filePath = this.getDirentPath(currentObject); + seafileAPI.sdocUnmaskAsDraft(repoID, filePath).then((res) => { + this.props.updateDirent(currentObject, 'is_sdoc_draft', false); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + + onStartRevise = (currentObject) => { + let repoID = this.props.repoID; + let filePath = this.getDirentPath(currentObject); + seafileAPI.sdocStartRevise(repoID, filePath).then((res) => { + let url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(res.data.file_path); + window.open(url); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + onCommentItem = () => { this.props.showDirentDetail('comments'); } diff --git a/frontend/src/components/dirent-list-view/dirent-list-item.js b/frontend/src/components/dirent-list-view/dirent-list-item.js index 199bc3b1ac..d9a2bbf7c8 100644 --- a/frontend/src/components/dirent-list-view/dirent-list-item.js +++ b/frontend/src/components/dirent-list-view/dirent-list-item.js @@ -270,6 +270,9 @@ class DirentListItem extends React.Component { case 'Unmask as draft': this.onUnmaskAsDraft(); break; + case 'Start revise': + this.onStartRevise(); + break; case 'Comment': this.props.onDirentClick(this.props.dirent); this.props.showDirentDetail('comments'); @@ -381,6 +384,18 @@ class DirentListItem extends React.Component { }); } + onStartRevise = () => { + let repoID = this.props.repoID; + let filePath = this.getDirentPath(this.props.dirent); + seafileAPI.sdocStartRevise(repoID, filePath).then((res) => { + let url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(res.data.file_path); + window.open(url); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + onHistory = () => { let repoID = this.props.repoID; let filePath = this.getDirentPath(this.props.dirent); diff --git a/frontend/src/components/toolbar/multiple-dir-operation-toolbar.js b/frontend/src/components/toolbar/multiple-dir-operation-toolbar.js index 7387f9d4a7..0c45006bc9 100644 --- a/frontend/src/components/toolbar/multiple-dir-operation-toolbar.js +++ b/frontend/src/components/toolbar/multiple-dir-operation-toolbar.js @@ -121,6 +121,40 @@ class MultipleDirOperationToolbar extends React.Component { }); } + onMaskAsDraft = (dirent) => { + let repoID = this.props.repoID; + let filePath = this.getDirentPath(dirent); + seafileAPI.sdocMaskAsDraft(repoID, filePath).then((res) => { + this.props.updateDirent(dirent, 'is_sdoc_draft', true); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + + onUnmaskAsDraft = (dirent) => { + let repoID = this.props.repoID; + let filePath = this.getDirentPath(dirent); + seafileAPI.sdocUnmaskAsDraft(repoID, filePath).then((res) => { + this.props.updateDirent(dirent, 'is_sdoc_draft', false); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + + onStartRevise = (dirent) => { + let repoID = this.props.repoID; + let filePath = this.getDirentPath(dirent); + seafileAPI.sdocStartRevise(repoID, filePath).then((res) => { + let url = siteRoot + 'lib/' + repoID + '/file' + Utils.encodePath(res.data.file_path); + window.open(url); + }).catch(error => { + let errMessage = Utils.getErrorMsg(error); + toaster.danger(errMessage); + }); + } + onCommentItem = () => { this.props.showDirentDetail('comments'); } @@ -171,6 +205,15 @@ class MultipleDirOperationToolbar extends React.Component { case 'Unlock': this.unlockFile(dirent); break; + case 'Mask as draft': + this.onMaskAsDraft(dirent); + break; + case 'Unmask as draft': + this.onUnmaskAsDraft(dirent); + break; + case 'Start revise': + this.onStartRevise(dirent); + break; case 'Comment': this.onCommentItem(); break; diff --git a/frontend/src/utils/text-translation.js b/frontend/src/utils/text-translation.js index b9525a938f..1a02c976c8 100644 --- a/frontend/src/utils/text-translation.js +++ b/frontend/src/utils/text-translation.js @@ -18,6 +18,7 @@ const TextTranslation = { 'UNLOCK' : {key : 'Unlock', value : gettext('Unlock')}, 'MASK_AS_DRAFT' : {key : 'Mask as draft', value : gettext('Mark as draft')}, 'UNMASK_AS_DRAFT' : {key : 'Unmask as draft', value : gettext('Unmark as draft')}, + 'START_REVISE' : {key : 'Start revise', value : gettext('Start revise')}, 'COMMENT' : {key : 'Comment', value : gettext('Comment')}, 'HISTORY' : {key : 'History', value : gettext('History')}, 'ACCESS_LOG' : {key : 'Access Log', value : gettext('Access Log')}, diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js index 82b7853b1d..f63d6c440e 100644 --- a/frontend/src/utils/utils.js +++ b/frontend/src/utils/utils.js @@ -527,7 +527,7 @@ export const Utils = { getFileOperationList: function(isRepoOwner, currentRepoInfo, dirent, isContextmenu) { let list = []; const { SHARE, DOWNLOAD, DELETE, RENAME, MOVE, COPY, TAGS, UNLOCK, LOCK, MASK_AS_DRAFT, UNMASK_AS_DRAFT, - COMMENT, HISTORY, ACCESS_LOG, OPEN_VIA_CLIENT, ONLYOFFICE_CONVERT } = TextTranslation; + START_REVISE, COMMENT, HISTORY, ACCESS_LOG, OPEN_VIA_CLIENT, ONLYOFFICE_CONVERT } = TextTranslation; const permission = dirent.permission; const { isCustomPermission, customPermission } = Utils.getUserPermission(permission); @@ -600,6 +600,7 @@ export const Utils = { } else { list.push(MASK_AS_DRAFT); } + list.push(START_REVISE); } if (enableFileComment) { list.push(COMMENT); diff --git a/seahub/seadoc/apis.py b/seahub/seadoc/apis.py index 6f6839c547..8fc0119f2c 100644 --- a/seahub/seadoc/apis.py +++ b/seahub/seadoc/apis.py @@ -40,6 +40,7 @@ from seahub.base.templatetags.seahub_tags import email2nickname, \ from seahub.utils.timeutils import utc_datetime_to_isoformat_timestr, timestamp_to_isoformat_timestr from seahub.base.models import FileComment from seahub.constants import PERMISSION_READ_WRITE +from seahub.seadoc.sdoc_server_api import SdocServerAPI logger = logging.getLogger(__name__) @@ -516,36 +517,44 @@ class SeadocDrafts(APIView): permission_classes = (IsAuthenticated,) throttle_classes = (UserRateThrottle, ) - def get(self, request, repo_id): + def get(self, request): """list drafts """ username = request.user.username # argument check - owned = request.GET.get('owned') + repo_id = request.GET.get('repo_id') + try: + page = int(request.GET.get('page', '1')) + per_page = int(request.GET.get('per_page', '25')) + except ValueError: + page = 1 + per_page = 25 + start = (page - 1) * per_page + limit = per_page + 1 - # 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) + if repo_id: + # 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 - permission = check_folder_permission(request, repo_id, '/') - if not permission: - error_msg = 'Permission denied.' - return api_error(status.HTTP_403_FORBIDDEN, error_msg) + # permission check + permission = check_folder_permission(request, repo_id, '/') + if not permission: + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) - # - if owned: - draft_queryset = SeadocDraft.objects.filter( - repo_id=repo_id, username=username) - # all + draft_queryset = SeadocDraft.objects.list_by_repo_id(repo_id, start, limit) + count = SeadocDraft.objects.filter(repo_id=repo_id).count() else: - draft_queryset = SeadocDraft.objects.list_by_repo_id(repo_id) + # owned + draft_queryset = SeadocDraft.objects.list_by_username(username, start, limit) + count = SeadocDraft.objects.filter(username=username).count() drafts = [draft.to_dict() for draft in draft_queryset] - return Response({'drafts': drafts}) + return Response({'drafts': drafts, 'count': count}) class SeadocMaskAsDraft(APIView): @@ -825,11 +834,15 @@ class SeadocRevisions(APIView): """ username = request.user.username # argument check - owned = request.GET.get('owned') repo_id = request.GET.get('repo_id') - if not repo_id or not owned: - error_msg = 'repo_id or owned invalid.' - return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + try: + page = int(request.GET.get('page', '1')) + per_page = int(request.GET.get('per_page', '25')) + except ValueError: + page = 1 + per_page = 25 + start = (page - 1) * per_page + limit = per_page + 1 if repo_id: # resource check @@ -842,15 +855,23 @@ class SeadocRevisions(APIView): if not permission: error_msg = 'Permission denied.' return api_error(status.HTTP_403_FORBIDDEN, error_msg) - - revision_queryset = SeadocRevision.objects.list_by_repo_id(repo_id) - # - elif owned: - revision_queryset = SeadocRevision.objects.list_by_username(username) - revisions = [revision.to_dict() for revision in revision_queryset] + revision_queryset = SeadocRevision.objects.list_by_repo_id(repo_id, start, limit) + count = SeadocRevision.objects.filter(repo_id=repo_id).count() + else: + # owned + revision_queryset = SeadocRevision.objects.list_by_username(username, start, limit) + count = SeadocRevision.objects.filter(username=username).count() - return Response({'revisions': revisions}) + uuid_set = set() + for item in revision_queryset: + uuid_set.add(item.doc_uuid) + uuid_set.add(item.origin_doc_uuid) + + fileuuidmap_queryset = FileUUIDMap.objects.filter(uuid__in=list(uuid_set)) + revisions = [revision.to_dict(fileuuidmap_queryset) for revision in revision_queryset] + + return Response({'revisions': revisions, 'count': count}) def post(self, request): """create @@ -861,7 +882,7 @@ class SeadocRevisions(APIView): if not path: error_msg = 'p invalid.' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) - repo_id = request.GET.get('repo_id') + repo_id = request.data.get('repo_id') if not repo_id: error_msg = 'repo_id invalid.' return api_error(status.HTTP_400_BAD_REQUEST, error_msg) @@ -910,8 +931,9 @@ class SeadocRevisions(APIView): revision_uuid_map = FileUUIDMap( uuid=revision_file_uuid, repo_id=repo_id, - parent_path=parent_dir, + parent_path='/Revisions', filename=revision_filename, + is_dir=False, ) revision_uuid_map.save() @@ -919,7 +941,7 @@ class SeadocRevisions(APIView): doc_uuid=revision_file_uuid, origin_doc_uuid=origin_file_uuid, repo_id=repo_id, - origin_file_path=path, + origin_doc_path=path, username=username, origin_file_version=origin_file_id, ) @@ -942,6 +964,9 @@ class SeadocPublishRevision(APIView): if not revision: error_msg = 'Revision %s not found.' % file_uuid return api_error(status.HTTP_404_NOT_FOUND, error_msg) + if revision.is_published: + error_msg = 'Revision %s is already published.' % file_uuid + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) repo_id = revision.repo_id repo = seafile_api.get_repo(repo_id) @@ -973,6 +998,7 @@ class SeadocPublishRevision(APIView): else: origin_file_parent_path = os.path.dirname(revision.origin_doc_path) origin_file_filename = os.path.basename(revision.origin_doc_path) + origin_file_path = posixpath.join(origin_file_parent_path, origin_file_filename) # check if origin file's parent folder exists if not seafile_api.get_dir_id_by_path(repo_id, origin_file_parent_path): @@ -982,7 +1008,7 @@ class SeadocPublishRevision(APIView): # get revision file info revision_file_uuid = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid) - revision_parent_path = revision_file_uuid.parent_pat + revision_parent_path = revision_file_uuid.parent_path revision_filename = revision_file_uuid.filename # move revision file @@ -994,7 +1020,13 @@ class SeadocPublishRevision(APIView): replace=1, username=username, need_progress=0, synchronous=1) - dst_file_id = seafile_api.get_file_id_by_path(repo_id, origin_file_filename) - revision = SeadocRevision.objects.publish( - file_uuid, username, dst_file_id) + dst_file_id = seafile_api.get_file_id_by_path(repo_id, origin_file_path) + SeadocRevision.objects.publish(file_uuid, username, dst_file_id) + + # refresh origin doc + sdoc_server_api = SdocServerAPI( + revision.origin_doc_uuid, origin_file_filename, username) + sdoc_server_api.refresh_doc() + + revision = SeadocRevision.objects.get_by_doc_uuid(file_uuid) return Response(revision.to_dict()) diff --git a/seahub/seadoc/models.py b/seahub/seadoc/models.py index 84e2fca306..639466361d 100644 --- a/seahub/seadoc/models.py +++ b/seahub/seadoc/models.py @@ -1,4 +1,5 @@ import os +import posixpath from django.db import models @@ -52,11 +53,11 @@ class SeadocDraftManager(models.Manager): def list_by_doc_uuids(self, doc_uuid_list): return self.filter(doc_uuid__in=doc_uuid_list) - def list_by_username(self, username): - return self.filter(username=username) + def list_by_username(self, username, start, limit): + return self.filter(username=username)[start:limit] - def list_by_repo_id(self, repo_id): - return self.filter(repo_id=repo_id) + def list_by_repo_id(self, repo_id, start, limit): + return self.filter(repo_id=repo_id)[start:limit] class SeadocDraft(models.Model): @@ -89,11 +90,11 @@ class SeadocRevisionManager(models.Manager): def list_by_origin_doc_uuid(self, origin_doc_uuid): return self.filter(origin_doc_uuid=origin_doc_uuid) - def list_by_username(self, username): - return self.filter(username=username) + def list_by_username(self, username, start, limit): + return self.filter(username=username)[start:limit] - def list_by_repo_id(self, repo_id): - return self.filter(repo_id=repo_id) + def list_by_repo_id(self, repo_id, start, limit): + return self.filter(repo_id=repo_id)[start:limit] def publish(self, doc_uuid, publisher, publish_file_version): return self.filter(doc_uuid=doc_uuid).update( @@ -123,24 +124,44 @@ class SeadocRevision(models.Model): class Meta: db_table = 'sdoc_revision' - def to_dict(self): + def to_dict(self, fileuuidmap_queryset=None): from seahub.tags.models import FileUUIDMap - file_uuid = FileUUIDMap.objects.get_fileuuidmap_by_uuid(self.origin_doc_uuid) - if file_uuid: - origin_parent_path = file_uuid.parent_path - origin_filename = file_uuid.filename + if fileuuidmap_queryset: + origin_doc_uuid = fileuuidmap_queryset.filter(uuid=self.origin_doc_uuid).first() + else: + origin_doc_uuid = FileUUIDMap.objects.get_fileuuidmap_by_uuid(self.origin_doc_uuid) + if origin_doc_uuid: + origin_parent_path = origin_doc_uuid.parent_path + origin_filename = origin_doc_uuid.filename else: origin_parent_path = os.path.dirname(self.origin_doc_path) origin_filename = os.path.basename(self.origin_doc_path) + origin_file_path = posixpath.join(origin_parent_path, origin_filename) + + if fileuuidmap_queryset: + doc_uuid = fileuuidmap_queryset.filter(uuid=self.doc_uuid).first() + else: + doc_uuid = FileUUIDMap.objects.get_fileuuidmap_by_uuid(self.doc_uuid) + if doc_uuid: + parent_path = doc_uuid.parent_path + filename = doc_uuid.filename + else: + parent_path = '/Revisions' + filename = self.doc_uuid + '.sdoc' + file_path = posixpath.join(parent_path, filename) return { 'username': self.username, 'nickname': email2nickname(self.username), 'repo_id': self.repo_id, 'doc_uuid': self.doc_uuid, + 'parent_path': parent_path, + 'filename': filename, + 'file_path': file_path, 'origin_doc_uuid': self.origin_doc_uuid, 'origin_parent_path': origin_parent_path, 'origin_filename': origin_filename, + 'origin_file_path': origin_file_path, 'origin_file_version': self.origin_file_version, 'publish_file_version': self.publish_file_version, 'publisher': self.publisher, diff --git a/seahub/seadoc/sdoc_server_api.py b/seahub/seadoc/sdoc_server_api.py new file mode 100644 index 0000000000..7982eb4f45 --- /dev/null +++ b/seahub/seadoc/sdoc_server_api.py @@ -0,0 +1,38 @@ +import json +import requests + +from seahub.settings import SEADOC_SERVER_URL +from seahub.seadoc.utils import gen_seadoc_access_token + + +def parse_response(response): + if response.status_code >= 400: + raise ConnectionError(response.status_code, response.text) + else: + try: + data = json.loads(response.text) + return data + except: + pass + + +class SdocServerAPI(object): + + def __init__(self, doc_uuid, filename, username): + self.doc_uuid = doc_uuid + self.filename = filename + self.username = username + self.headers = None + self.sdoc_server_url = SEADOC_SERVER_URL.rstrip('/') + self.timeout = 30 + self._init() + + def _init(self): + sdoc_server_access_token = gen_seadoc_access_token( + self.doc_uuid, self.filename, self.username) + self.headers = {'Authorization': 'Token ' + sdoc_server_access_token} + + def refresh_doc(self): + url = self.sdoc_server_url + '/api/v1/docs/' + self.doc_uuid + '/internal-refresh-doc/?from=seahub' + response = requests.post(url, headers=self.headers) + return parse_response(response) diff --git a/seahub/seadoc/urls.py b/seahub/seadoc/urls.py index 27a8568978..e9727e9276 100644 --- a/seahub/seadoc/urls.py +++ b/seahub/seadoc/urls.py @@ -12,10 +12,10 @@ urlpatterns = [ re_path(r'^download-image/(?P[-0-9a-f]{36})/(?P.*)$', SeadocDownloadImage.as_view(), name='seadoc_download_image'), re_path(r'^copy-history-file/(?P[-0-9a-f]{36})/$', SeadocCopyHistoryFile.as_view(), name='seadoc_copy_history_file'), re_path(r'^history/(?P[-0-9a-f]{36})/$', SeadocHistory.as_view(), name='seadoc_history'), - re_path(r'^drafts/(?P[-0-9a-f]{36})/$', SeadocDrafts.as_view(), name='seadoc_drafts'), + re_path(r'^drafts/$', SeadocDrafts.as_view(), name='seadoc_drafts'), re_path(r'^mask-as-draft/(?P[-0-9a-f]{36})/$', SeadocMaskAsDraft.as_view(), name='seadoc_mask_as_draft'), re_path(r'^comments/(?P[-0-9a-f]{36})/$', SeadocCommentsView.as_view(), name='seadoc_comments'), re_path(r'^comment/(?P[-0-9a-f]{36})/(?P\d+)/$', SeadocCommentView.as_view(), name='seadoc_comment'), re_path(r'^revisions/$', SeadocRevisions.as_view(), name='seadoc_revisions'), - re_path(r'^publish-revision/$', SeadocPublishRevision.as_view(), name='seadoc_publish_revision'), + re_path(r'^publish-revision/(?P[-0-9a-f]{36})/$', SeadocPublishRevision.as_view(), name='seadoc_publish_revision'), ] diff --git a/sql/mysql.sql b/sql/mysql.sql index aa6a0da2ea..73b94a3572 100644 --- a/sql/mysql.sql +++ b/sql/mysql.sql @@ -1403,12 +1403,14 @@ CREATE TABLE `sdoc_revision` ( `repo_id` varchar(36) NOT NULL, `doc_uuid` varchar(36) NOT NULL, `origin_doc_uuid` varchar(36) NOT NULL, + `origin_doc_path` longtext NOT NULL, `origin_file_version` varchar(100) NOT NULL, `publish_file_version` varchar(100) DEFAULT NULL, `username` varchar(255) NOT NULL, + `publisher` varchar(255) DEFAULT NULL, + `is_published` tinyint(1) NOT NULL, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, - `is_published` tinyint(1) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `sdoc_revise_doc_uuid` (`doc_uuid`), KEY `sdoc_revision_repo_id` (`repo_id`),