diff --git a/seahub/seadoc/apis.py b/seahub/seadoc/apis.py index 18730806df..6f6839c547 100644 --- a/seahub/seadoc/apis.py +++ b/seahub/seadoc/apis.py @@ -1,5 +1,6 @@ import os import json +import uuid import logging import requests import posixpath @@ -32,12 +33,13 @@ from seahub.tags.models import FileUUIDMap from seahub.utils.error_msg import file_type_error_msg from seahub.utils.repo import parse_repo_perm from seahub.utils.file_revisions import get_file_revisions_within_limit -from seahub.seadoc.models import SeadocHistoryName, SeadocDraft +from seahub.seadoc.models import SeadocHistoryName, SeadocDraft, SeadocRevision from seahub.avatar.templatetags.avatar_tags import api_avatar_url from seahub.base.templatetags.seahub_tags import email2nickname, \ email2contact_email 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 logger = logging.getLogger(__name__) @@ -810,3 +812,189 @@ class SeadocCommentView(APIView): comment = file_comment.to_dict() comment.update(user_to_dict(file_comment.author, request=request, avatar_size=avatar_size)) return Response(comment) + + +class SeadocRevisions(APIView): + + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticated,) + throttle_classes = (UserRateThrottle, ) + + def get(self, request): + """list + """ + 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) + + 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) + + 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] + + return Response({'revisions': revisions}) + + def post(self, request): + """create + """ + username = request.user.username + # argument check + path = request.data.get('p', None) + if not path: + error_msg = 'p invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + repo_id = request.GET.get('repo_id') + if not repo_id: + error_msg = 'repo_id invalid.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + path = normalize_file_path(path) + parent_dir = os.path.dirname(path) + filename = os.path.basename(path) + + filetype, fileext = get_file_type_and_ext(filename) + if filetype != SEADOC: + error_msg = 'seadoc file type %s invalid.' % filetype + 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 + permission = check_folder_permission(request, repo_id, '/') + if not permission: + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + # create revision dir if does not exist + revision_dir_id = seafile_api.get_dir_id_by_path(repo_id, '/Revisions') + if revision_dir_id is None: + seafile_api.post_dir(repo_id, '/', 'Revisions', username) + + # + origin_file_uuid = get_seadoc_file_uuid(repo, path) + origin_file_id = seafile_api.get_file_id_by_path(repo_id, path) + revision_file_uuid = str(uuid.uuid4()) + revision_filename = revision_file_uuid + '.sdoc' + + # copy file to revision dir + seafile_api.copy_file( + repo_id, parent_dir, + json.dumps([filename]), + repo_id, '/Revisions', + json.dumps([revision_filename]), + username=username, need_progress=0, synchronous=1 + ) + + revision_uuid_map = FileUUIDMap( + uuid=revision_file_uuid, + repo_id=repo_id, + parent_path=parent_dir, + filename=revision_filename, + ) + revision_uuid_map.save() + + revision = SeadocRevision.objects.create( + doc_uuid=revision_file_uuid, + origin_doc_uuid=origin_file_uuid, + repo_id=repo_id, + origin_file_path=path, + username=username, + origin_file_version=origin_file_id, + ) + + return Response(revision.to_dict()) + + +class SeadocPublishRevision(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + permission_classes = (IsAuthenticated, ) + throttle_classes = (UserRateThrottle, ) + + def post(self, request, file_uuid): + """publish + """ + force = request.data.get('force') # used when origin file deleted + + # resource check + revision = SeadocRevision.objects.get_by_doc_uuid(file_uuid) + if not revision: + error_msg = 'Revision %s not found.' % file_uuid + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + repo_id = revision.repo_id + 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) + + if permission != PERMISSION_READ_WRITE: + error_msg = 'Permission denied.' + return api_error(status.HTTP_403_FORBIDDEN, error_msg) + + # get origin file info + origin_file_uuid = FileUUIDMap.objects.get_fileuuidmap_by_uuid( + revision.origin_doc_uuid) + + if not origin_file_uuid and not force: + error_msg = 'origin sdoc %s not found.' % file_uuid + return api_error(status.HTTP_404_NOT_FOUND, error_msg) + + if origin_file_uuid: + origin_file_parent_path = origin_file_uuid.parent_path + origin_file_filename = origin_file_uuid.filename + else: + origin_file_parent_path = os.path.dirname(revision.origin_doc_path) + origin_file_filename = os.path.basename(revision.origin_doc_path) + + # check if origin file's parent folder exists + if not seafile_api.get_dir_id_by_path(repo_id, origin_file_parent_path): + dst_parent_path = '/' + else: + dst_parent_path = origin_file_parent_path + + # get revision file info + revision_file_uuid = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid) + revision_parent_path = revision_file_uuid.parent_pat + revision_filename = revision_file_uuid.filename + + # move revision file + username = request.user.username + seafile_api.move_file(repo_id, revision_parent_path, + json.dumps([revision_filename]), + repo_id, dst_parent_path, + json.dumps([origin_file_filename]), + 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) + return Response(revision.to_dict()) diff --git a/seahub/seadoc/models.py b/seahub/seadoc/models.py index 7db77c44f6..84e2fca306 100644 --- a/seahub/seadoc/models.py +++ b/seahub/seadoc/models.py @@ -1,6 +1,9 @@ +import os + from django.db import models from seahub.utils.timeutils import datetime_to_isoformat_timestr +from seahub.base.templatetags.seahub_tags import email2nickname class SeadocHistoryNameManager(models.Manager): @@ -28,7 +31,6 @@ class SeadocHistoryName(models.Model): def to_dict(self): return { - 'id': self.pk, 'doc_uuid': self.doc_uuid, 'obj_id': self.obj_id, 'name': self.name, @@ -75,3 +77,75 @@ class SeadocDraft(models.Model): 'username': self.username, 'created_at': datetime_to_isoformat_timestr(self.created_at), } + +class SeadocRevisionManager(models.Manager): + + def get_by_doc_uuid(self, doc_uuid): + return self.filter(doc_uuid=doc_uuid).first() + + def list_by_doc_uuids(self, doc_uuid_list): + return self.filter(doc_uuid__in=doc_uuid_list) + + 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_repo_id(self, repo_id): + return self.filter(repo_id=repo_id) + + def publish(self, doc_uuid, publisher, publish_file_version): + return self.filter(doc_uuid=doc_uuid).update( + publisher=publisher, + publish_file_version=publish_file_version, + is_published=True, + ) + + +class SeadocRevision(models.Model): + """ + """ + doc_uuid = models.CharField(max_length=36, unique=True) + origin_doc_uuid = models.CharField(max_length=36, db_index=True) + repo_id = models.CharField(max_length=36, db_index=True) + origin_doc_path = models.TextField() # used when origin file deleted + username = models.CharField(max_length=255, db_index=True) + origin_file_version = models.CharField(max_length=100) + publish_file_version = models.CharField(max_length=100, null=True) + publisher = models.CharField(max_length=255, null=True) + is_published = models.BooleanField(default=False) + created_at = models.DateTimeField(auto_now_add=True, db_index=True) + updated_at = models.DateTimeField(auto_now=True, db_index=True) + + objects = SeadocRevisionManager() + + class Meta: + db_table = 'sdoc_revision' + + def to_dict(self): + 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 + else: + origin_parent_path = os.path.dirname(self.origin_doc_path) + origin_filename = os.path.basename(self.origin_doc_path) + + return { + 'username': self.username, + 'nickname': email2nickname(self.username), + 'repo_id': self.repo_id, + 'doc_uuid': self.doc_uuid, + 'origin_doc_uuid': self.origin_doc_uuid, + 'origin_parent_path': origin_parent_path, + 'origin_filename': origin_filename, + 'origin_file_version': self.origin_file_version, + 'publish_file_version': self.publish_file_version, + 'publisher': self.publisher, + 'publisher_nickname': email2nickname(self.publisher), + 'is_published': self.is_published, + 'created_at': datetime_to_isoformat_timestr(self.created_at), + 'updated_at': datetime_to_isoformat_timestr(self.updated_at), + } diff --git a/seahub/seadoc/urls.py b/seahub/seadoc/urls.py index dfbf56be43..27a8568978 100644 --- a/seahub/seadoc/urls.py +++ b/seahub/seadoc/urls.py @@ -1,7 +1,7 @@ from django.urls import re_path from .apis import SeadocAccessToken, SeadocUploadLink, SeadocDownloadLink, SeadocUploadFile, \ SeadocUploadImage, SeadocDownloadImage, SeadocCopyHistoryFile, SeadocHistory, SeadocDrafts, SeadocMaskAsDraft, \ - SeadocCommentsView, SeadocCommentView + SeadocCommentsView, SeadocCommentView, SeadocRevisions, SeadocPublishRevision urlpatterns = [ re_path(r'^access-token/(?P[-0-9a-f]{36})/$', SeadocAccessToken.as_view(), name='seadoc_access_token'), @@ -16,4 +16,6 @@ urlpatterns = [ 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'), ] diff --git a/sql/mysql.sql b/sql/mysql.sql index ce0d955442..aa6a0da2ea 100644 --- a/sql/mysql.sql +++ b/sql/mysql.sql @@ -1397,3 +1397,23 @@ CREATE TABLE `sdoc_draft` ( KEY `sdoc_draft_repo_id` (`repo_id`), KEY `sdoc_draft_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE `sdoc_revision` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `repo_id` varchar(36) NOT NULL, + `doc_uuid` varchar(36) NOT NULL, + `origin_doc_uuid` varchar(36) NOT NULL, + `origin_file_version` varchar(100) NOT NULL, + `publish_file_version` varchar(100) DEFAULT NULL, + `username` varchar(255) 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`), + KEY `sdoc_revision_origin_doc_uuid` (`origin_doc_uuid`), + KEY `sdoc_revision_username` (`username`), + KEY `sdoc_revision_created_at` (`created_at`), + KEY `sdoc_revision_updated_at` (`updated_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;