1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-12 13:24:52 +00:00
Files
seahub/seahub/seadoc/apis.py
Stephen 8ef2c35331 Feature/adjust inserting sdoc file link (#7190)
* insert sdoc file link directly

* adjust search files api
2024-12-21 13:49:25 +08:00

3181 lines
126 KiB
Python

import os
import re
import json
import uuid
import stat
import logging
import requests
import posixpath
from urllib.parse import unquote, quote
import time
import shutil
from datetime import datetime, timedelta
from pypinyin import lazy_pinyin
from zipfile import is_zipfile, ZipFile
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from django.utils.translation import gettext as _
from django.http import HttpResponseRedirect, HttpResponse, FileResponse, HttpResponseNotModified
from django.core.files.base import ContentFile
from django.utils import timezone
from django.db import transaction
from django.urls import reverse
from django.core.files.uploadhandler import TemporaryFileUploadHandler
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django.views.decorators.http import condition
from seaserv import seafile_api, check_quota, get_org_id_by_repo_id, ccnet_api
from seahub.utils.ccnet_db import CcnetDB
from seahub.views import check_folder_permission
from seahub.api2.authentication import TokenAuthentication, SdocJWTTokenAuthentication, CsrfExemptSessionAuthentication
from seahub.api2.permissions import IsProVersion
from seahub.api2.utils import api_error, user_to_dict, to_python_boolean, get_user_common_info
from seahub.api2.throttling import UserRateThrottle
from seahub.seadoc.utils import is_valid_seadoc_access_token, get_seadoc_upload_link, \
get_seadoc_download_link, get_seadoc_file_uuid, gen_seadoc_access_token, \
gen_seadoc_image_parent_path, get_seadoc_asset_upload_link, get_seadoc_asset_download_link, \
can_access_seadoc_asset, is_seadoc_revision, ZSDOC, export_sdoc
from seahub.seadoc.settings import SDOC_REVISIONS_DIR, SDOC_IMAGES_DIR
from seahub.utils.file_types import SEADOC, IMAGE, VIDEO
from seahub.utils.file_op import if_locked_by_online_office
from seahub.utils import get_file_type_and_ext, normalize_file_path, \
normalize_dir_path, PREVIEW_FILEEXT, \
gen_inner_file_get_url, gen_inner_file_upload_url, gen_file_get_url, \
get_service_url, is_valid_username, is_pro_version, \
get_file_history_by_day, get_file_daily_history_detail, HAS_FILE_SEARCH, HAS_FILE_SEASEARCH, gen_file_upload_url, \
get_non_sdoc_file_exts
from seahub.tags.models import FileUUIDMap
from seahub.utils.error_msg import file_type_error_msg
from seahub.utils.repo import parse_repo_perm, get_related_users_by_repo
from seahub.seadoc.models import SeadocHistoryName, SeadocRevision, SeadocCommentReply, SeadocNotification
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, datetime_to_isoformat_timestr
from seahub.base.models import FileComment
from seahub.constants import PERMISSION_READ_WRITE, PERMISSION_INVISIBLE
from seahub.seadoc.sdoc_server_api import SdocServerAPI
from seahub.file_participants.models import FileParticipant
from seahub.base.accounts import User
from seahub.avatar.settings import AVATAR_DEFAULT_SIZE
from seahub.repo_tags.models import RepoTags
from seahub.file_tags.models import FileTags
from seahub.wiki2.models import Wiki2Publish
if HAS_FILE_SEARCH or HAS_FILE_SEASEARCH:
from seahub.search.utils import search_files, ai_search_files, format_repos
logger = logging.getLogger(__name__)
try:
TO_TZ = time.strftime('%z')[:3] + ':' + time.strftime('%z')[3:]
except Exception as error:
TO_TZ = '+00:00'
HTTP_443_ABOVE_QUOTA = 443
HTTP_447_TOO_MANY_FILES_IN_LIBRARY = 447
class SeadocAccessToken(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
def get(self, request, repo_id):
username = request.user.username
# argument check
path = request.GET.get('p', None)
if not path:
error_msg = 'p 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)
try:
obj_id = seafile_api.get_file_id_by_path(repo_id, path)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
if not obj_id:
error_msg = 'File %s not found.' % path
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# permission check
permission = check_folder_permission(request, repo_id, parent_dir)
if not permission:
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
#
file_uuid = get_seadoc_file_uuid(repo, path)
access_token = gen_seadoc_access_token(file_uuid, filename, username, permission=permission)
return Response({'access_token': access_token})
class SeadocUploadFile(APIView):
authentication_classes = ()
throttle_classes = (UserRateThrottle,)
def post(self, request, file_uuid):
# jwt permission check
auth = request.headers.get('authorization', '').split()
if not is_valid_seadoc_access_token(auth, file_uuid):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
file = request.FILES.get('file', None)
if not file:
error_msg = 'file not found.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
filetype, fileext = get_file_type_and_ext(uuid_map.filename)
if filetype != SEADOC:
error_msg = 'seadoc file type %s invalid.' % filetype
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
file_path = posixpath.join(uuid_map.parent_path, uuid_map.filename)
file_id = seafile_api.get_file_id_by_path(uuid_map.repo_id, file_path)
if not file_id: # save file anyway
seafile_api.post_empty_file(
uuid_map.repo_id, uuid_map.parent_path, uuid_map.filename, '')
#
last_modify_user = request.POST.get('last_modify_user', '')
upload_link = get_seadoc_upload_link(uuid_map, last_modify_user)
if not upload_link:
error_msg = 'seadoc file %s not found.' % uuid_map.filename
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# update file
files = {'file': file}
data = {'filename': uuid_map.filename, 'target_file': file_path}
resp = requests.post(upload_link, files=files, data=data)
if not resp.ok:
logger.error('save sdoc failed %s, %s' % (file_uuid, resp.text))
return api_error(resp.status_code, resp.content)
return Response({'success': True})
class SeadocUploadLink(APIView):
authentication_classes = ()
throttle_classes = (UserRateThrottle,)
def get(self, request, file_uuid):
# jwt permission check
auth = request.headers.get('authorization', '').split()
if not is_valid_seadoc_access_token(auth, file_uuid):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
filetype, fileext = get_file_type_and_ext(uuid_map.filename)
if filetype != SEADOC:
error_msg = 'seadoc file type %s invalid.' % filetype
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
file_path = posixpath.join(uuid_map.parent_path, uuid_map.filename)
file_id = seafile_api.get_file_id_by_path(uuid_map.repo_id, file_path)
if not file_id: # save file anyway
seafile_api.post_empty_file(
uuid_map.repo_id, uuid_map.parent_path, uuid_map.filename, '')
upload_link = get_seadoc_upload_link(uuid_map)
if not upload_link:
error_msg = 'seadoc file %s not found.' % uuid_map.filename
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
return Response({
'upload_link': upload_link,
'parent_dir': uuid_map.parent_path,
'filename': uuid_map.filename,
})
class SeadocDownloadLink(APIView):
authentication_classes = ()
throttle_classes = (UserRateThrottle,)
def get(self, request, file_uuid):
# jwt permission check
auth = request.headers.get('authorization', '').split()
if not is_valid_seadoc_access_token(auth, file_uuid):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
filetype, fileext = get_file_type_and_ext(uuid_map.filename)
if filetype != SEADOC:
error_msg = 'seadoc file type %s invalid.' % filetype
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
#
download_link = get_seadoc_download_link(uuid_map)
if not download_link:
error_msg = 'seadoc file %s not found.' % uuid_map.filename
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
return Response({'download_link': download_link})
class SeadocImageDownloadLink(APIView):
authentication_classes = ()
throttle_classes = (UserRateThrottle,)
def get(self, request, file_uuid):
# jwt permission check
auth = request.headers.get('authorization', '').split()
if not is_valid_seadoc_access_token(auth, file_uuid):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
# argument check
image_name = request.GET.get('image_name')
if not image_name:
error_msg = 'image_name invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# recource check
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
image_path = f'{SDOC_IMAGES_DIR}{file_uuid}/{image_name}'
file_id = seafile_api.get_file_id_by_path(repo_id, image_path)
if not file_id:
error_msg = f'image {image_path} not found.'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
token = seafile_api.get_fileserver_access_token(repo_id, file_id, 'download', "")
download_link = gen_file_get_url(token, image_name)
return Response({'download_link': download_link})
class SeadocOriginFileContent(APIView):
authentication_classes = ()
throttle_classes = (UserRateThrottle,)
def get(self, request, file_uuid):
# jwt permission check
auth = request.headers.get('authorization', '').split()
if not is_valid_seadoc_access_token(auth, file_uuid):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
filetype, fileext = get_file_type_and_ext(uuid_map.filename)
if filetype != SEADOC:
error_msg = 'seadoc file type %s invalid.' % filetype
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
revision_info = is_seadoc_revision(file_uuid)
origin_doc_uuid = revision_info.get('origin_doc_uuid', '')
is_published = revision_info.get('is_published', False)
if is_published:
error_msg = 'seadoc file %s has publish.' % uuid_map.filename
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
origin_uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(origin_doc_uuid)
if not origin_uuid_map:
error_msg = 'seadoc origin uuid %s not found.' % origin_doc_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# get content from sdoc server
username = request.user.username
sdoc_server_api = SdocServerAPI(origin_doc_uuid, str(origin_uuid_map.filename), username)
try:
res = sdoc_server_api.get_doc()
return Response({
'content': json.dumps(res)
})
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
class SeadocUploadImage(APIView):
authentication_classes = ()
throttle_classes = (UserRateThrottle,)
def post(self, request, file_uuid):
"""image path: /images/sdoc/${sdocUuid}/${filename}
"""
# jwt permission check
auth = request.headers.get('authorization', '').split()
is_valid, payload = is_valid_seadoc_access_token(auth, file_uuid, return_payload=True)
if not is_valid:
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
file_list = request.FILES.getlist('file')
if not file_list or not isinstance(file_list, list):
error_msg = 'Image can not be found.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# max 10 images
file_list = file_list[:10]
for file in file_list:
file_type, ext = get_file_type_and_ext(file.name)
if file_type != IMAGE:
error_msg = file_type_error_msg(ext, PREVIEW_FILEEXT.get('Image'))
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
if check_quota(uuid_map.repo_id) < 0:
error_msg = _("Out of quota.")
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# main
repo_id = uuid_map.repo_id
username = payload.get('username', '')
parent_path = gen_seadoc_image_parent_path(file_uuid, repo_id, username)
upload_link = get_seadoc_asset_upload_link(repo_id, parent_path, username)
relative_path = []
for file in file_list:
file_path = posixpath.join(parent_path, file.name)
files = {'file': file}
data = {'parent_dir': parent_path, 'filename': file.name, 'target_file': file_path}
resp = requests.post(upload_link, files=files, data=data)
if not resp.ok:
logger.error(resp.text)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
image_url = '/' + file.name
relative_path.append(image_url)
return Response({'relative_path': relative_path})
def latest_entry(request, file_uuid, filename):
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if uuid_map:
try:
repo_id = uuid_map.repo_id
username = request.user.username
parent_path = gen_seadoc_image_parent_path(file_uuid, repo_id, username)
filepath = os.path.join(parent_path, filename)
file_obj = seafile_api.get_dirent_by_path(repo_id, filepath)
dt = datetime.fromtimestamp(file_obj.mtime)
return dt
except Exception as e:
logger.error(e)
return None
else:
return None
class SeadocDownloadImage(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = ()
throttle_classes = (UserRateThrottle, )
@method_decorator(condition(last_modified_func=latest_entry))
def get(self, request, file_uuid, filename):
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
username = request.user.username
wiki_publish = Wiki2Publish.objects.filter(repo_id=repo_id).first()
# permission check
if not wiki_publish:
file_path = posixpath.join(uuid_map.parent_path, uuid_map.filename)
if not can_access_seadoc_asset(request, repo_id, file_path, file_uuid):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
# main
parent_path = gen_seadoc_image_parent_path(file_uuid, repo_id, username)
download_link = get_seadoc_asset_download_link(repo_id, parent_path, filename, username)
if not download_link:
error_msg = 'file %s not found.' % filename
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
resp = requests.get(download_link)
if not resp.ok:
logger.error(resp.text)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
filetype, fileext = get_file_type_and_ext(filename)
response = HttpResponse(
content=resp.content, content_type='image/' + fileext)
response['Cache-Control'] = 'private, max-age=%s' % (3600 * 24 * 7)
return response
class SeadocUploadVideo(APIView):
authentication_classes = ()
throttle_classes = (UserRateThrottle,)
def post(self, request, file_uuid):
"""video path: /images/sdoc/${sdocUuid}/${filename}
"""
# jwt permission check
auth = request.headers.get('authorization', '').split()
is_valid, payload = is_valid_seadoc_access_token(auth, file_uuid, return_payload=True)
if not is_valid:
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
file_list = request.FILES.getlist('file')
if not file_list or not isinstance(file_list, list):
error_msg = 'Video can not be found.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# max 10 videos
file_list = file_list[:10]
for file in file_list:
file_type, ext = get_file_type_and_ext(file.name)
if file_type != VIDEO:
error_msg = file_type_error_msg(ext, PREVIEW_FILEEXT.get('VIDEO'))
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
if check_quota(uuid_map.repo_id) < 0:
error_msg = _("Out of quota.")
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# main
repo_id = uuid_map.repo_id
username = payload.get('username', '')
parent_path = gen_seadoc_image_parent_path(file_uuid, repo_id, username)
upload_link = get_seadoc_asset_upload_link(repo_id, parent_path, username)
relative_path = []
for file in file_list:
file_path = posixpath.join(parent_path, file.name)
files = {'file': file}
data = {'parent_dir': parent_path, 'filename': file.name, 'target_file': file_path}
resp = requests.post(upload_link, files=files, data=data)
if not resp.ok:
logger.error(resp.text)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
image_url = '/' + file.name
relative_path.append(image_url)
return Response({'relative_path': relative_path})
class SeadocDownloadVideo(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = ()
throttle_classes = (UserRateThrottle, )
@method_decorator(condition(last_modified_func=latest_entry))
def get(self, request, file_uuid, filename):
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
username = request.user.username
wiki_publish = Wiki2Publish.objects.filter(repo_id=repo_id).first()
# permission check
if not wiki_publish:
file_path = posixpath.join(uuid_map.parent_path, uuid_map.filename)
if not can_access_seadoc_asset(request, repo_id, file_path, file_uuid):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
# main
parent_path = gen_seadoc_image_parent_path(file_uuid, repo_id, username)
download_link = get_seadoc_asset_download_link(repo_id, parent_path, filename, username)
if not download_link:
error_msg = 'file %s not found.' % filename
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
resp = requests.get(download_link)
if not resp.ok:
logger.error(resp.text)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
filetype, fileext = get_file_type_and_ext(filename)
response = HttpResponse(
content=resp.content, content_type='video/' + fileext)
response['Cache-Control'] = 'private, max-age=%s' % (3600 * 24 * 7)
return response
class SeadocAsyncCopyImages(APIView):
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated, )
throttle_classes = (UserRateThrottle, )
def post(self, request, file_uuid):
username = request.user.username
origin_doc_uuid = request.data.get('origin_doc_uuid', '')
if file_uuid == origin_doc_uuid:
error_msg = 'file_uuid and origin_doc_uuid can not be the same.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if not origin_doc_uuid:
error_msg = 'origin_doc_uuid invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
image_list = request.data.get('image_list', [])
if not image_list or not isinstance(image_list, list):
error_msg = 'image_list invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# dst
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
if check_quota(repo_id) < 0:
error_msg = _("Out of quota.")
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
image_parent_path = gen_seadoc_image_parent_path(file_uuid, repo_id, username)
if not check_folder_permission(request, repo_id, image_parent_path):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
# origin
origin_uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(origin_doc_uuid)
if not origin_uuid_map:
error_msg = 'seadoc uuid %s not found.' % origin_doc_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
origin_repo_id = origin_uuid_map.repo_id
origin_image_parent_path = gen_seadoc_image_parent_path(origin_doc_uuid, origin_repo_id, username)
if not check_folder_permission(request, origin_repo_id, origin_image_parent_path):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
# copy image files
res = seafile_api.copy_file(
origin_repo_id, origin_image_parent_path,
json.dumps(image_list),
repo_id, image_parent_path,
json.dumps(image_list),
username=username, need_progress=1, synchronous=0
)
task_id = res.task_id if res.background else ''
return Response({'task_id': task_id})
class SeadocQueryCopyMoveProgressView(APIView):
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle,)
def get(self, request, file_uuid):
""" Fetch progress of file/dir mv/cp.
"""
# argument check
task_id = request.GET.get('task_id')
if not task_id:
error_msg = 'task_id invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
try:
res = seafile_api.get_copy_task(task_id)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
# res can be None
if not res:
error_msg = _('Error')
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
result = {}
result['done'] = res.done
result['total'] = res.total
result['canceled'] = res.canceled
result['failed'] = res.failed
result['successful'] = res.successful
return Response(result)
class SeadocCopyHistoryFile(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
def post(self, request, repo_id):
username = request.user.username
obj_id = request.data.get('obj_id', '')
path = request.data.get('p', '')
ctime = request.data.get('ctime', '')
# only check the permissions at the repo level
# to prevent file can not be copied on the history page
if not parse_repo_perm(check_folder_permission(request, repo_id, '/')).can_copy:
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
# new file name
file_name = os.path.basename(path.rstrip('/'))
parent_dir = os.path.dirname(path)
new_file_name = '.'.join(file_name.split('.')[0:-1]) + \
'(' + str(ctime) + ').' + file_name.split('.')[-1]
new_file_path = posixpath.join(parent_dir, new_file_name)
# download
token = seafile_api.get_fileserver_access_token(repo_id,
obj_id, 'download', username)
if not token:
error_msg = 'file %s not found.' % obj_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
download_url = gen_inner_file_get_url(token, file_name)
resp = requests.get(download_url)
if not resp.ok:
logger.error(resp.text)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
content = resp.content
file = ContentFile(content)
file.name = new_file_name
# upload
obj_id = json.dumps({'parent_dir': parent_dir})
token = seafile_api.get_fileserver_access_token(
repo_id, obj_id, 'upload-link', username, use_onetime=True)
if not token:
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
upload_link = gen_inner_file_upload_url('upload-api', token)
files = {'file': file}
data = {'parent_dir': parent_dir, 'filename': new_file_name, 'target_file': new_file_path}
resp = requests.post(upload_link, files=files, data=data)
if not resp.ok:
logger.error(resp.text)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
return Response({
'file_name': new_file_name,
'file_path': new_file_path,
})
class SeadocHistory(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
def _get_new_file_history_info(self, ent, name_dict):
info = {}
creator_name = ent.get('op_user')
url, is_default, date_uploaded = api_avatar_url(creator_name)
info['creator_avatar_url'] = url
info['creator_email'] = creator_name
info['creator_name'] = email2nickname(creator_name)
info['creator_contact_email'] = email2contact_email(creator_name)
info['ctime'] = utc_datetime_to_isoformat_timestr(ent.get('timestamp'))
info['size'] = ent.get('size')
info['obj_id'] = ent.get('file_id')
info['commit_id'] = ent.get('commit_id')
info['old_path'] = ent.get('old_path', '')
info['path'] = ent.get('path')
info['name'] = name_dict.get(ent.get('file_id', ''), '')
info['count'] = ent.get('count', 1)
info['date'] = ent.get('date', '')
info['id'] = ent.get('id', '')
return info
def get(self, request, file_uuid):
"""list history, same as NewFileHistoryView
"""
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
username = request.user.username
path = posixpath.join(uuid_map.parent_path, uuid_map.filename)
# permission check
if not check_folder_permission(request, repo_id, path):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, 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)
commit_id = repo.head_cmmt_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
# Don't use seafile_api.get_file_id_by_path()
# if path parameter is `rev_renamed_old_path`.
# seafile_api.get_file_id_by_path() will return None.
file_id = seafile_api.get_file_id_by_commit_and_path(repo_id,
commit_id, path)
if not file_id:
error_msg = 'File %s not found.' % path
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# get repo history limit
try:
history_limit = seafile_api.get_repo_history_limit(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)
start = (page - 1) * per_page
count = per_page
to_tz = request.GET.get('to_tz', TO_TZ)
try:
file_revisions = get_file_history_by_day(repo_id, path, start, count, to_tz, history_limit)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
name_dict = {}
obj_id_list = [commit.get('file_id', '') for commit in file_revisions]
if obj_id_list:
name_queryset = SeadocHistoryName.objects.list_by_obj_ids(
doc_uuid=file_uuid, obj_id_list=obj_id_list)
name_dict = {item.obj_id: item.name for item in name_queryset}
data = [self._get_new_file_history_info(ent, name_dict) for ent in file_revisions]
result = {
"histories": data
}
return Response(result)
def post(self, request, file_uuid):
"""rename history
"""
username = request.user.username
obj_id = request.data.get('obj_id', '')
new_name = request.data.get('new_name', '')
if not obj_id:
error_msg = 'obj_id invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if not new_name:
error_msg = 'new_name invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
username = request.user.username
path = posixpath.join(uuid_map.parent_path, uuid_map.filename)
# permission check
if not check_folder_permission(request, repo_id, path):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, 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)
token = seafile_api.get_fileserver_access_token(repo_id,
obj_id, 'download', username)
if not token:
error_msg = 'history %s not found.' % obj_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# main
SeadocHistoryName.objects.update_name(file_uuid, obj_id, new_name)
return Response({
'obj_id': obj_id,
'name': new_name,
})
class SeadocDailyHistoryDetail(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
def _get_new_file_history_info(self, ent, name_dict):
info = {}
creator_name = ent.op_user
url, is_default, date_uploaded = api_avatar_url(creator_name)
info['creator_avatar_url'] = url
info['creator_email'] = creator_name
info['creator_name'] = email2nickname(creator_name)
info['creator_contact_email'] = email2contact_email(creator_name)
info['ctime'] = utc_datetime_to_isoformat_timestr(ent.timestamp)
info['size'] = ent.size
info['obj_id'] = ent.file_id
info['commit_id'] = ent.commit_id
info['old_path'] = ent.old_path if hasattr(ent, 'old_path') else ''
info['path'] = ent.path
info['name'] = name_dict.get(ent.file_id, '')
info['count'] = 1
info['id'] = ent.id
return info
def get(self, request, file_uuid):
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
username = request.user.username
path = posixpath.join(uuid_map.parent_path, uuid_map.filename)
# permission check
if not check_folder_permission(request, repo_id, path):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, 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)
op_date = request.GET.get('op_date', None)
if not op_date:
error_msg = 'op_date invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
try:
# op_date eg: 2023-05-16T07:30:49+08:00
op_date_format = datetime.strptime(op_date, '%Y-%m-%dT%H:%M:%S%z')
to_tz = op_date[-6:]
start_time = op_date_format.replace(hour=0, minute=0, second=0, tzinfo=None)
end_time = start_time + timedelta(days=1)
except Exception as e:
logger.error(e)
error_msg = 'op_date invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
try:
file_revisions = get_file_daily_history_detail(repo_id, path, start_time, end_time, to_tz)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
name_dict = {}
obj_id_list = [commit.file_id for commit in file_revisions]
if obj_id_list:
name_queryset = SeadocHistoryName.objects.list_by_obj_ids(
doc_uuid=file_uuid, obj_id_list=obj_id_list)
name_dict = {item.obj_id: item.name for item in name_queryset}
data = [self._get_new_file_history_info(ent, name_dict) for ent in file_revisions]
result = {
"histories": data[1:]
}
return Response(result)
class SeadocNotificationsView(APIView):
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
def get(self, request, file_uuid):
"""list unseen notifications
"""
username = request.user.username
try:
avatar_size = int(request.GET.get('avatar_size', AVATAR_DEFAULT_SIZE))
except ValueError:
avatar_size = AVATAR_DEFAULT_SIZE
# resource check
notifications = []
try:
notifications_query = SeadocNotification.objects.list_by_unseen(
file_uuid, username)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
for notification in notifications_query:
data = notification.to_dict()
data.update(
user_to_dict(notification.username, request=request))
notifications.append(data)
result = {'notifications': notifications}
return Response(result)
def put(self, request, file_uuid):
""" mark all notifications seen
"""
username = request.user.username
try:
SeadocNotification.objects.filter(
doc_uuid=file_uuid, username=username, seen=False).update(seen=True)
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})
def delete(self, request, file_uuid):
"""delete notifications seen
"""
username = request.user.username
ids = request.data.get('ids')
try:
SeadocNotification.objects.delete_by_ids(file_uuid, username, ids)
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})
class SeadocNotificationView(APIView):
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
def put(self, request, file_uuid, notification_id):
""" mark a notification seen
"""
username = request.user.username
# resource check
notification = SeadocNotification.objects.filter(
id=notification_id).first()
if not notification:
error_msg = 'Notification %s not found.' % notification_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# permission check
if notification.username != username or notification.doc_uuid != file_uuid:
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
if not notification.seen:
try:
notification.seen = True
notification.save()
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})
class SeadocCommentsView(APIView):
authentication_classes = ()
throttle_classes = (UserRateThrottle,)
def get(self, request, file_uuid):
"""list comments of a sdoc, same as FileCommentsView
"""
auth = request.headers.get('authorization', '').split()
if not is_valid_seadoc_access_token(auth, file_uuid):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
try:
avatar_size = int(request.GET.get('avatar_size', AVATAR_DEFAULT_SIZE))
except ValueError:
avatar_size = AVATAR_DEFAULT_SIZE
resolved = request.GET.get('resolved', None)
if resolved not in ('true', 'false', None):
error_msg = 'resolved invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
start = None
end = None
page = request.GET.get('page', '')
if page:
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
end = page * per_page
total_count = FileComment.objects.list_by_file_uuid(file_uuid).count()
comments = []
if resolved is None:
file_comments = FileComment.objects.list_by_file_uuid(file_uuid)[start: end]
else:
comment_resolved = to_python_boolean(resolved)
file_comments = FileComment.objects.list_by_file_uuid(file_uuid).filter(resolved=comment_resolved)[start: end]
reply_queryset = SeadocCommentReply.objects.list_by_doc_uuid(file_uuid)
for file_comment in file_comments:
comment = file_comment.to_dict(reply_queryset)
comment.update(user_to_dict(file_comment.author, request=request))
comments.append(comment)
result = {'comments': comments, 'total_count': total_count}
return Response(result)
def post(self, request, file_uuid):
"""Post a comment of a sdoc.
"""
# argument check
auth = request.headers.get('authorization', '').split()
is_valid, payload = is_valid_seadoc_access_token(auth, file_uuid, return_payload=True)
if not is_valid:
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
try:
avatar_size = int(request.GET.get('avatar_size', AVATAR_DEFAULT_SIZE))
except ValueError:
avatar_size = AVATAR_DEFAULT_SIZE
comment = request.data.get('comment', '')
detail = request.data.get('detail', '')
author = request.data.get('author', '')
username = payload.get('username', '') or author
if comment is None:
return api_error(status.HTTP_400_BAD_REQUEST, 'comment invalid.')
if not username:
return api_error(status.HTTP_400_BAD_REQUEST, 'author invalid.')
file_comment = FileComment.objects.add_by_file_uuid(
file_uuid, username, comment, detail)
comment = file_comment.to_dict()
comment.update(user_to_dict(username, request=request))
# notification
to_users = set()
participant_queryset = FileParticipant.objects.get_participants(file_uuid)
for participant in participant_queryset:
to_users.add(participant.username)
to_users.discard(username) # remove author
to_users = list(to_users)
detail = {
'author': username,
'comment_id': int(file_comment.id),
'comment' : str(file_comment.comment),
'msg_type': 'comment',
'created_at': datetime_to_isoformat_timestr(file_comment.created_at),
'updated_at': datetime_to_isoformat_timestr(file_comment.updated_at),
}
detail.update(user_to_dict(username, request=request))
new_notifications = []
for to_user in to_users:
new_notifications.append(
SeadocNotification(
doc_uuid=file_uuid,
username=to_user,
msg_type='comment',
detail=json.dumps(detail),
))
try:
SeadocNotification.objects.bulk_create(new_notifications)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
#
notification = detail
notification['to_users'] = to_users
comment['notification'] = notification
return Response(comment)
class SeadocCommentView(APIView):
authentication_classes = ()
throttle_classes = (UserRateThrottle,)
def get(self, request, file_uuid, comment_id):
"""Get a comment, same as FileCommentView
"""
auth = request.headers.get('authorization', '').split()
if not is_valid_seadoc_access_token(auth, file_uuid):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
try:
avatar_size = int(request.GET.get('avatar_size', AVATAR_DEFAULT_SIZE))
except ValueError:
avatar_size = AVATAR_DEFAULT_SIZE
# resource check
try:
file_comment = FileComment.objects.get(pk=comment_id)
except FileComment.DoesNotExist:
return api_error(status.HTTP_400_BAD_REQUEST, 'Wrong comment id')
if str(file_comment.uuid.uuid) != file_uuid:
return api_error(status.HTTP_404_NOT_FOUND, 'comment not found: %s' % comment_id)
comment = file_comment.to_dict()
comment.update(user_to_dict(
file_comment.author, request=request))
return Response(comment)
def delete(self, request, file_uuid, comment_id):
"""Delete a comment
"""
auth = request.headers.get('authorization', '').split()
if not is_valid_seadoc_access_token(auth, file_uuid):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
# resource check
try:
file_comment = FileComment.objects.get(pk=comment_id)
except FileComment.DoesNotExist:
return api_error(status.HTTP_400_BAD_REQUEST, 'Wrong comment id')
if str(file_comment.uuid.uuid) != file_uuid:
return api_error(status.HTTP_404_NOT_FOUND, 'comment not found: %s' % comment_id)
file_comment.delete()
SeadocCommentReply.objects.filter(comment_id=comment_id).delete()
return Response({'success': True})
def put(self, request, file_uuid, comment_id):
"""Update a comment
"""
auth = request.headers.get('authorization', '').split()
if not is_valid_seadoc_access_token(auth, file_uuid):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
try:
avatar_size = int(request.GET.get('avatar_size', AVATAR_DEFAULT_SIZE))
except ValueError:
avatar_size = AVATAR_DEFAULT_SIZE
# argument check
resolved = request.data.get('resolved')
if resolved not in ('true', 'false', None):
error_msg = 'resolved invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
detail = request.data.get('detail')
comment = request.data.get('comment')
# resource check
try:
file_comment = FileComment.objects.get(pk=comment_id)
except FileComment.DoesNotExist:
error_msg = 'FileComment %s not found.' % comment_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
if str(file_comment.uuid.uuid) != file_uuid:
return api_error(status.HTTP_404_NOT_FOUND, 'comment not found: %s' % comment_id)
if resolved is not None:
# do not refresh updated_at
comment_resolved = to_python_boolean(resolved)
file_comment.resolved = comment_resolved
file_comment.save(update_fields=['resolved'])
if detail is not None or comment is not None:
if detail is not None:
file_comment.detail = detail
if comment is not None:
file_comment.comment = comment
# save
file_comment.updated_at = timezone.now()
file_comment.save()
comment = file_comment.to_dict()
comment.update(user_to_dict(file_comment.author, request=request))
return Response(comment)
class SeadocCommentRepliesView(APIView):
authentication_classes = ()
throttle_classes = (UserRateThrottle,)
def get(self, request, file_uuid, comment_id):
"""list comment replies of a sdoc
"""
auth = request.headers.get('authorization', '').split()
if not is_valid_seadoc_access_token(auth, file_uuid):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
try:
avatar_size = int(request.GET.get('avatar_size', AVATAR_DEFAULT_SIZE))
except ValueError:
avatar_size = AVATAR_DEFAULT_SIZE
start = None
end = None
page = request.GET.get('page', '')
if page:
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
end = page * per_page
# resource check
file_comment = FileComment.objects.filter(
id=comment_id, uuid=file_uuid).first()
if not file_comment:
return api_error(status.HTTP_404_NOT_FOUND, 'comment not found.')
total_count = SeadocCommentReply.objects.list_by_comment_id(comment_id).count()
replies = []
reply_queryset = SeadocCommentReply.objects.list_by_comment_id(comment_id)[start: end]
for reply in reply_queryset:
data = reply.to_dict()
data.update(
user_to_dict(reply.author, request=request))
replies.append(data)
result = {'replies': replies, 'total_count': total_count}
return Response(result)
def post(self, request, file_uuid, comment_id):
"""post a comment reply of a sdoc.
"""
# argument check
auth = request.headers.get('authorization', '').split()
is_valid, payload = is_valid_seadoc_access_token(auth, file_uuid, return_payload=True)
if not is_valid:
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
try:
avatar_size = int(request.GET.get('avatar_size', AVATAR_DEFAULT_SIZE))
except ValueError:
avatar_size = AVATAR_DEFAULT_SIZE
reply_content = request.data.get('reply', '')
type_content = request.data.get('type', 'reply')
author = request.data.get('author', '')
username = payload.get('username', '') or author
if reply_content is None:
return api_error(status.HTTP_400_BAD_REQUEST, 'reply invalid.')
if not username:
return api_error(status.HTTP_400_BAD_REQUEST, 'author invalid.')
# resource check
file_comment = FileComment.objects.filter(
id=comment_id, uuid=file_uuid).first()
if not file_comment:
return api_error(status.HTTP_404_NOT_FOUND, 'comment not found.')
reply = SeadocCommentReply.objects.create(
author=username,
reply=str(reply_content),
type=str(type_content),
comment_id=comment_id,
doc_uuid=file_uuid,
)
data = reply.to_dict()
data.update(
user_to_dict(reply.author, request=request))
# notification
to_users = set()
participant_queryset = FileParticipant.objects.get_participants(file_uuid)
for participant in participant_queryset:
to_users.add(participant.username)
to_users.discard(username) # remove author
to_users = list(to_users)
detail = {
'author': username,
'comment_id': int(comment_id),
'reply_id': reply.pk,
'reply': str(reply_content),
'msg_type': 'reply',
'created_at': datetime_to_isoformat_timestr(reply.created_at),
'updated_at': datetime_to_isoformat_timestr(reply.updated_at),
'is_resolved': type(reply_content) is bool and reply_content is True,
'resolve_comment': file_comment.comment.strip()
}
detail.update(user_to_dict(username, request=request))
new_notifications = []
for to_user in to_users:
new_notifications.append(
SeadocNotification(
doc_uuid=file_uuid,
username=to_user,
msg_type='reply',
detail=json.dumps(detail),
))
try:
SeadocNotification.objects.bulk_create(new_notifications)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
#
notification = detail
notification['to_users'] = to_users
data['notification'] = notification
return Response(data)
class SeadocCommentReplyView(APIView):
authentication_classes = ()
throttle_classes = (UserRateThrottle,)
def get(self, request, file_uuid, comment_id, reply_id):
"""Get a comment reply
"""
auth = request.headers.get('authorization', '').split()
if not is_valid_seadoc_access_token(auth, file_uuid):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
try:
avatar_size = int(request.GET.get('avatar_size', AVATAR_DEFAULT_SIZE))
except ValueError:
avatar_size = AVATAR_DEFAULT_SIZE
# resource check
file_comment = FileComment.objects.filter(
id=comment_id, uuid=file_uuid).first()
if not file_comment:
return api_error(status.HTTP_404_NOT_FOUND, 'comment not found.')
reply = SeadocCommentReply.objects.filter(
id=reply_id, doc_uuid=file_uuid, comment_id=comment_id).first()
if not reply:
return api_error(status.HTTP_404_NOT_FOUND, 'reply not found.')
data = reply.to_dict()
data.update(
user_to_dict(reply.author, request=request))
return Response(data)
def delete(self, request, file_uuid, comment_id, reply_id):
"""Delete a comment reply
"""
auth = request.headers.get('authorization', '').split()
if not is_valid_seadoc_access_token(auth, file_uuid):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
# resource check
file_comment = FileComment.objects.filter(
id=comment_id, uuid=file_uuid).first()
if not file_comment:
return api_error(status.HTTP_404_NOT_FOUND, 'comment not found.')
reply = SeadocCommentReply.objects.filter(
id=reply_id, doc_uuid=file_uuid, comment_id=comment_id).first()
if not reply:
return api_error(status.HTTP_404_NOT_FOUND, 'reply not found.')
reply.delete()
return Response({'success': True})
def put(self, request, file_uuid, comment_id, reply_id):
"""Update a comment reply
"""
auth = request.headers.get('authorization', '').split()
if not is_valid_seadoc_access_token(auth, file_uuid):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
# argument check
reply_content = request.data.get('reply')
if reply_content is None:
return api_error(status.HTTP_400_BAD_REQUEST, 'reply invalid.')
try:
avatar_size = int(request.GET.get('avatar_size', AVATAR_DEFAULT_SIZE))
except ValueError:
avatar_size = AVATAR_DEFAULT_SIZE
# resource check
file_comment = FileComment.objects.filter(
id=comment_id, uuid=file_uuid).first()
if not file_comment:
return api_error(status.HTTP_404_NOT_FOUND, 'comment not found.')
reply = SeadocCommentReply.objects.filter(
id=reply_id, doc_uuid=file_uuid, comment_id=comment_id).first()
if not reply:
return api_error(status.HTTP_404_NOT_FOUND, 'reply not found.')
# save
reply.reply = str(reply_content)
reply.updated_at = timezone.now()
reply.save()
data = reply.to_dict()
data.update(
user_to_dict(reply.author, request=request))
return Response(data)
class SdocRepoTagsView(APIView):
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle,)
def get(self, request, file_uuid):
"""list all repo_tags by repo_id.
"""
# resource check
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
# permission check
if not check_folder_permission(request, repo_id, '/'):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
# get files tags
repo_tags = []
try:
repo_tag_list = RepoTags.objects.get_all_by_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)
for repo_tag in repo_tag_list:
res = repo_tag.to_dict()
repo_tags.append(res)
return Response({"repo_tags": repo_tags}, status=status.HTTP_200_OK)
def post(self, request, file_uuid):
"""add one repo_tag.
"""
# argument check
tag_name = request.data.get('name')
if not tag_name:
error_msg = 'name invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
tag_color = request.data.get('color')
if not tag_color:
error_msg = 'color invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# resource check
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
repo_tag = RepoTags.objects.get_repo_tag_by_name(repo_id, tag_name)
if repo_tag:
error_msg = 'repo tag %s already exist.' % tag_name
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# permission check
if check_folder_permission(request, repo_id, '/') != PERMISSION_READ_WRITE:
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
try:
repo_tag = RepoTags.objects.create_repo_tag(repo_id, tag_name, tag_color)
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({"repo_tag": repo_tag.to_dict()}, status=status.HTTP_201_CREATED)
def put(self, request, file_uuid):
"""bulk add repo_tags.
"""
# argument check
tags = request.data.get('tags')
if not tags:
error_msg = 'tags invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# resource check
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
# permission check
if check_folder_permission(request, repo_id, '/') != PERMISSION_READ_WRITE:
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
tag_objs = list()
try:
for tag in tags:
name = tag.get('name' ,'')
color = tag.get('color', '')
if name and color:
obj = RepoTags(repo_id=repo_id, name=name, color=color)
tag_objs.append(obj)
except Exception as e:
logger.error(e)
error_msg = 'tags invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
try:
repo_tag_list = RepoTags.objects.bulk_create(tag_objs)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
repo_tags = list()
for repo_tag in repo_tag_list:
res = repo_tag.to_dict()
repo_tags.append(res)
return Response({"repo_tags": repo_tags}, status=status.HTTP_200_OK)
class SdocRepoTagView(APIView):
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle,)
def put(self, request, file_uuid, repo_tag_id):
"""update one repo_tag
"""
# argument check
tag_name = request.data.get('name')
if not tag_name:
error_msg = 'name invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
tag_color = request.data.get('color')
if not tag_color:
error_msg = 'color invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# resource check
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
repo_tag = RepoTags.objects.get_repo_tag_by_id(repo_tag_id)
if not repo_tag:
error_msg = 'repo_tag not found.'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# permission check
if check_folder_permission(request, repo_id, '/') != PERMISSION_READ_WRITE:
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
try:
repo_tag.name = tag_name
repo_tag.color = tag_color
repo_tag.save()
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({"repo_tag": repo_tag.to_dict()}, status=status.HTTP_200_OK)
def delete(self, request, file_uuid, repo_tag_id):
"""delete one repo_tag
"""
# resource check
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
repo_tag = RepoTags.objects.get_repo_tag_by_id(repo_tag_id)
if not repo_tag:
error_msg = 'repo_tag not found.'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# permission check
if check_folder_permission(request, repo_id, '/') != PERMISSION_READ_WRITE:
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
try:
RepoTags.objects.delete_repo_tag(repo_tag_id)
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}, status=status.HTTP_200_OK)
class SdocRepoFileTagsView(APIView):
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle,)
def get(self, request, file_uuid):
"""list all tags of a file.
"""
# resource check
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
# permission check
if not check_folder_permission(request, repo_id, '/'):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
try:
file_tags = FileTags.objects.list_file_tags_by_file_uuid(uuid_map)
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({"file_tags": file_tags}, status=status.HTTP_200_OK)
def post(self, request, file_uuid):
"""add a tag for a file.
"""
# argument check
repo_tag_id = request.data.get('repo_tag_id')
if not repo_tag_id:
error_msg = 'repo_tag_id invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# resource check
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
repo_tag = RepoTags.objects.get_repo_tag_by_id(repo_tag_id)
if not repo_tag:
error_msg = 'repo_tag not found.'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
file_tag = FileTags.objects.get_file_tag_by_file_uuid(uuid_map, repo_tag_id)
if file_tag:
error_msg = 'file tag %s already exist.' % repo_tag_id
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# permission check
if check_folder_permission(request, repo_id, '/') != PERMISSION_READ_WRITE:
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
try:
file_tag = FileTags.objects.add_file_tag_by_file_uuid(uuid_map, repo_tag_id)
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({"file_tag": file_tag.to_dict()}, status=status.HTTP_201_CREATED)
class SdocRepoFileTagView(APIView):
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle,)
def delete(self, request, file_uuid, file_tag_id):
"""delete a tag from a file
"""
# resource check
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
file_tag = FileTags.objects.get_file_tag_by_id(file_tag_id)
if not file_tag:
error_msg = 'file_tag %s not found.' % file_tag_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# permission check
if check_folder_permission(request, repo_id, '/') != PERMISSION_READ_WRITE:
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
try:
FileTags.objects.delete_file_tag(file_tag_id)
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}, status=status.HTTP_200_OK)
class SeadocStartRevise(APIView):
# sdoc editor use jwt token
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
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.data.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, SDOC_REVISIONS_DIR)
if revision_dir_id is None:
seafile_api.mkdir_with_parents(repo_id, '/', SDOC_REVISIONS_DIR[1:], username)
#
origin_file_uuid = get_seadoc_file_uuid(repo, path)
if SeadocRevision.objects.get_by_doc_uuid(origin_file_uuid):
error_msg = 'seadoc %s is already a revision.' % filename
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# save origin file
try:
sdoc_server_api = SdocServerAPI(origin_file_uuid, filename, username)
res = sdoc_server_api.save_doc()
except Exception as e:
warning_msg = 'Save origin sdoc %s failed.' % origin_file_uuid
logger.warning(warning_msg)
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'
with transaction.atomic():
revision_uuid_map = FileUUIDMap(
uuid=revision_file_uuid,
repo_id=repo_id,
parent_path=SDOC_REVISIONS_DIR,
filename=revision_filename,
is_dir=False,
)
revision_uuid_map.save()
revision = SeadocRevision.objects.add(
doc_uuid=revision_file_uuid,
origin_doc_uuid=origin_file_uuid,
repo_id=repo_id,
origin_doc_path=path,
username=username,
origin_file_version=origin_file_id,
)
# copy file to revision dir
seafile_api.copy_file(
repo_id, parent_dir,
json.dumps([filename]),
repo_id, SDOC_REVISIONS_DIR,
json.dumps([revision_filename]),
username=username, need_progress=0, synchronous=1,
)
# copy image files
origin_image_parent_path = SDOC_IMAGES_DIR + origin_file_uuid + '/'
dir_id = seafile_api.get_dir_id_by_path(repo_id, origin_image_parent_path)
if dir_id:
revision_image_parent_path = gen_seadoc_image_parent_path(
revision_file_uuid, repo_id, username)
dirents = seafile_api.list_dir_by_path(repo_id, origin_image_parent_path)
obj_name_list = [item.obj_name for item in dirents]
if obj_name_list:
seafile_api.copy_file(
repo_id, origin_image_parent_path,
json.dumps(obj_name_list),
repo_id, revision_image_parent_path,
json.dumps(obj_name_list),
username=username, need_progress=0, synchronous=1
)
return Response(revision.to_dict())
class SeadocRevisionsCount(APIView):
# sdoc editor use jwt token
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
def get(self, request, file_uuid):
if not file_uuid:
error_msg = 'file_uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'file %s uuid_map not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
count = SeadocRevision.objects.filter(origin_doc_uuid=uuid_map.uuid, is_published=False).count()
return Response({
'count': count
})
class SeadocRevisions(APIView):
# sdoc editor use jwt token
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
def get(self, request, file_uuid):
if not file_uuid:
error_msg = 'file_uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'file %s uuid_map not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
count = SeadocRevision.objects.filter(origin_doc_uuid=uuid_map.uuid, is_published=False).count()
revision_queryset = SeadocRevision.objects.list_all_by_origin_doc_uuid(uuid_map.uuid)
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
end = page * per_page
revisions_queryset= revision_queryset[start:end]
uuid_set = set()
for item in revisions_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 revisions_queryset]
return Response({
'count': count,
'revisions': revisions
})
class SeadocRevisionView(APIView):
# sdoc editor use jwt token
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
def delete(self, request, file_uuid):
if not file_uuid:
error_msg = 'file_uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'file %s uuid_map not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
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)
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)
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 revision file info
revision_file_uuid = uuid_map
revision_parent_path = revision_file_uuid.parent_path
revision_filename = revision_file_uuid.filename
username = request.user.username
revision_image_parent_path = SDOC_IMAGES_DIR + str(revision_file_uuid.uuid) + '/'
dir_id = seafile_api.get_dir_id_by_path(repo_id, revision_image_parent_path)
if dir_id:
seafile_api.del_file(
repo_id, SDOC_IMAGES_DIR, json.dumps([str(revision_file_uuid.uuid)]), username)
seafile_api.del_file(
repo_id, revision_parent_path, json.dumps([revision_filename]), username)
SeadocRevision.objects.delete_by_doc_uuid(file_uuid)
sdoc_server_api = SdocServerAPI(file_uuid, revision_filename, username)
sdoc_server_api.remove_doc()
return Response({
'success': True
})
# modify by rebase op
def put(self, request, file_uuid):
if not file_uuid:
error_msg = 'file_uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'file %s uuid_map not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
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)
if revision.is_published:
error_msg = 'Revision %s is already published.' % file_uuid
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
origin_doc_uuid = revision.origin_doc_uuid
origin_uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(origin_doc_uuid)
if not origin_uuid_map:
error_msg = 'origin file %s uuid_map not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
file_path = posixpath.join(uuid_map.parent_path, uuid_map.filename)
file_id = seafile_api.get_file_id_by_path(uuid_map.repo_id, file_path)
if not file_id: # save file anyway
seafile_api.post_empty_file(
uuid_map.repo_id, uuid_map.parent_path, uuid_map.filename, '')
username = request.user.username
upload_link = get_seadoc_upload_link(uuid_map, username)
if not upload_link:
error_msg = 'seadoc file %s not found.' % uuid_map.filename
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
try:
# rewrite file
file = request.FILES.get('file', None)
files = {'file': file}
data = {'filename': uuid_map.filename, 'target_file': file_path}
resp = requests.post(upload_link, files=files, data=data)
if not resp.ok:
logger.error(resp.text)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
# update origin file version
origin_doc_path = revision.origin_doc_path
newest_file_version = seafile_api.get_file_id_by_path(origin_uuid_map.repo_id, origin_doc_path)
SeadocRevision.objects.update_origin_file_version(file_uuid, newest_file_version)
# server content update
sdoc_server_api = SdocServerAPI(file_uuid, str(uuid_map.filename), username)
sdoc_server_api.replace_doc()
return Response({
'origin_file_version': newest_file_version,
})
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
class DeleteSeadocOtherRevision(APIView):
# sdoc editor use jwt token
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
def delete(self, request, file_uuid, revision_id):
origin_doc_uuid = file_uuid
if not origin_doc_uuid:
error_msg = 'origin_file_uuid %s not found.' % origin_doc_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
origin_uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(origin_doc_uuid)
if not origin_uuid_map:
error_msg = 'file %s uuid_map not found.' % origin_doc_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
if not revision_id:
error_msg = 'Revision %s not found.' % revision_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
revision = SeadocRevision.objects.get_by_origin_doc_uuid_and_revision_id(origin_doc_uuid, revision_id)
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)
file_uuid = revision.doc_uuid
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'file %s uuid_map 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 revision file info
revision_file_uuid = uuid_map
revision_parent_path = revision_file_uuid.parent_path
revision_filename = revision_file_uuid.filename
username = request.user.username
revision_image_parent_path = SDOC_IMAGES_DIR + str(revision_file_uuid.uuid) + '/'
dir_id = seafile_api.get_dir_id_by_path(repo_id, revision_image_parent_path)
if dir_id:
seafile_api.del_file(
repo_id, SDOC_IMAGES_DIR, json.dumps([str(revision_file_uuid.uuid)]), username)
seafile_api.del_file(
repo_id, revision_parent_path, json.dumps([revision_filename]), username)
SeadocRevision.objects.delete_by_doc_uuid(file_uuid)
sdoc_server_api = SdocServerAPI(file_uuid, revision_filename, username)
sdoc_server_api.remove_doc()
return Response({
'success': True,
})
class SeadocPublishRevision(APIView):
authentication_classes = (SdocJWTTokenAuthentication, 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)
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)
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)
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):
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_path
revision_filename = revision_file_uuid.filename
# username
username = request.user.username
try:
sdoc_server_api = SdocServerAPI(file_uuid, revision_filename, username)
res = sdoc_server_api.save_doc()
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
# move revision file
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_path)
SeadocRevision.objects.publish(file_uuid, username, dst_file_id)
# move image files
revision_image_parent_path = SDOC_IMAGES_DIR + str(revision_file_uuid.uuid) + '/'
dir_id = seafile_api.get_dir_id_by_path(repo_id, revision_image_parent_path)
if dir_id:
origin_image_parent_path = gen_seadoc_image_parent_path(
str(origin_file_uuid.uuid), repo_id, username)
dirents = seafile_api.list_dir_by_path(repo_id, revision_image_parent_path)
obj_names = [e.obj_name for e in dirents]
if obj_names:
seafile_api.move_file(
repo_id, revision_image_parent_path,
json.dumps(obj_names),
repo_id, origin_image_parent_path,
json.dumps(obj_names),
replace=1, username=username,
need_progress=0, synchronous=1,
)
seafile_api.del_file(
repo_id, SDOC_IMAGES_DIR, json.dumps([str(revision_file_uuid.uuid)]), username)
try:
res = sdoc_server_api.publish_doc(str(origin_file_uuid.uuid), origin_file_filename)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
revision = SeadocRevision.objects.get_by_doc_uuid(file_uuid)
return Response(revision.to_dict())
class SeadocFileView(APIView):
"""redirect to file view by file_uuid
"""
authentication_classes = ()
throttle_classes = (UserRateThrottle,)
def get(self, request, file_uuid):
# do not permission check, just redirect
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'file uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
file_path = posixpath.join(uuid_map.parent_path, uuid_map.filename)
file_url = reverse('view_lib_file', args=[repo_id, file_path])
return HttpResponseRedirect(file_url)
class SeadocFileUUIDView(APIView):
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated, )
throttle_classes = (UserRateThrottle, )
def get(self, request, file_uuid):
""" Get file_uuid of a file/dir.
"""
sdoc_uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not sdoc_uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
path = request.GET.get('p', None)
if not path:
error_msg = 'path invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
is_dir = request.GET.get('is_dir', 'false')
is_dir = is_dir.lower()
if is_dir not in ('true', 'false'):
error_msg = "is_dir can only be 'true' or 'false'."
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# resource check
repo_id = sdoc_uuid_map.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)
is_dir = to_python_boolean(is_dir)
if is_dir:
if not seafile_api.get_dir_id_by_path(repo_id, normalize_dir_path(path)):
error_msg = 'Folder %s not found.' % path
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
else:
if not seafile_api.get_file_id_by_path(repo_id, normalize_file_path(path)):
error_msg = 'File %s not found.' % path
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# permission check
if not check_folder_permission(request, repo_id, path):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
# make sure path:
# 1. starts with '/'
# 2. NOT ends with '/'
path = normalize_file_path(path)
parent_dir = os.path.dirname(path)
dirent_name = os.path.basename(path)
# get file/dir uuid
if repo.is_virtual:
repo_id = repo.origin_repo_id
path = posixpath.join(repo.origin_path, path.strip('/'))
path = normalize_file_path(path)
parent_dir = os.path.dirname(path)
dirent_name = os.path.basename(path)
try:
uuid_map = FileUUIDMap.objects.get_or_create_fileuuidmap(repo_id,
parent_dir, dirent_name, is_dir)
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({'file_uuid': str(uuid_map.uuid)})
class SeadocFilesInfoView(APIView):
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated, )
throttle_classes = (UserRateThrottle, )
def post(self, request, file_uuid):
""" Get files info by file view url or smart link
"""
files_url = request.data.get('files_url', [])
if not files_url:
error_msg = 'files_url invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
service_url = get_service_url().strip('/')
smart_link_re = re.compile(r'^%s/smart-link/([-0-9a-f]{36})/$' % (service_url))
file_url_re = re.compile(r'^%s/lib/([-0-9a-f]{36})/file/(.*)' % (service_url))
dir_url_re = re.compile(r'^%s/library/([-0-9a-f]{36})/(.*)' % (service_url))
files_info = {}
for file_url in files_url:
file_url = unquote(file_url)
files_info[file_url] = {}
repo_id = ''
path = ''
dirent_file_uuid = ''
is_dir = False
try:
if smart_link_re.match(file_url) is not None:
re_result = smart_link_re.match(file_url)
dirent_file_uuid = re_result.group(1)
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(dirent_file_uuid)
if not uuid_map:
continue
repo_id = uuid_map.repo_id
path = posixpath.join(uuid_map.parent_path, uuid_map.filename)
is_dir = uuid_map.is_dir
elif file_url_re.match(file_url) is not None:
re_result = file_url_re.match(file_url)
repo_id = re_result.group(1)
path = re_result.group(2)
elif dir_url_re.match(file_url) is not None:
re_result = dir_url_re.match(file_url)
repo_id = re_result.group(1)
path = re_result.group(2).split('/', 1)[-1]
is_dir = True
# check
if not repo_id or not path:
continue
if is_dir:
path = normalize_dir_path(path)
path = path.rstrip('/')
parent_path = os.path.dirname(path)
file_name = os.path.basename(path)
else:
path = normalize_file_path(path)
parent_path = os.path.dirname(path)
file_name = os.path.basename(path)
# permission check
if not check_folder_permission(request, repo_id, path):
continue
except Exception as e:
logger.exception(e)
continue
info = {
'is_dir': is_dir,
'name': file_name,
'parent_path': parent_path,
'repo_id': repo_id,
'file_type': '',
'file_ext': '',
'file_uuid': dirent_file_uuid,
}
if not is_dir:
filetype, fileext = get_file_type_and_ext(file_name)
info['file_ext'] = fileext
info['file_type'] = filetype
if filetype == SEADOC and not dirent_file_uuid:
repo = seafile_api.get_repo(repo_id)
dirent_file_uuid = get_seadoc_file_uuid(repo, path)
info['file_uuid'] = dirent_file_uuid
files_info[file_url] = info
return Response({'files_info': files_info})
class SeadocDirView(APIView):
"""list all files in dir
"""
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated, )
throttle_classes = (UserRateThrottle, )
def get(self, request, file_uuid):
username = request.user.username
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
file_type = request.GET.get('type', 'sdoc') # sdoc, image, file
path = request.GET.get('p', '/')
path = normalize_dir_path(path)
repo_id = uuid_map.repo_id
dir_id = seafile_api.get_dir_id_by_path(repo_id, path)
if not dir_id:
error_msg = 'Folder %s not found.' % path
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# permission check
if not check_folder_permission(request, repo_id, path):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
try:
dirs = seafile_api.list_dir_with_perm(
repo_id, path, dir_id, username, -1, -1)
dirs = dirs if dirs else []
except Exception as e:
logger.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to list dir.")
uuid_map_queryset = FileUUIDMap.objects.get_fileuuidmaps_by_parent_path(repo_id, path)
dir_list, file_list = [], []
for dirent in dirs:
if dirent.permission == PERMISSION_INVISIBLE:
continue
entry = {}
if stat.S_ISDIR(dirent.mode):
dtype = "dir"
else:
dtype = "file"
filetype, fileext = get_file_type_and_ext(dirent.obj_name)
dirent_uuid_map = uuid_map_queryset.filter(filename=dirent.obj_name).first()
dirent_file_uuid = str(dirent_uuid_map.uuid) if dirent_uuid_map else ''
if file_type == 'sdoc' and filetype == SEADOC:
entry["file_uuid"] = dirent_file_uuid
elif filetype == 'image' and filetype == IMAGE:
entry["file_uuid"] = dirent_file_uuid
elif file_type == 'file' and filetype not in (SEADOC, IMAGE):
entry["file_uuid"] = dirent_file_uuid
else:
continue
entry["type"] = dtype
entry["name"] = dirent.obj_name
entry["id"] = dirent.obj_id
entry["mtime"] = dirent.mtime
entry["permission"] = dirent.permission
if dtype == 'dir':
dir_list.append(entry)
else:
file_list.append(entry)
dir_list.sort(key=lambda x: x['name'].lower())
file_list.sort(key=lambda x: x['name'].lower())
dentrys = dir_list + file_list
return Response(dentrys)
class SdocRevisionBaseVersionContent(APIView):
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
throttle_classes = (UserRateThrottle,)
def get(self, request, file_uuid):
if not file_uuid:
error_msg = 'file_uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'File %s uuid_map not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
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)
origin_doc_path = revision.origin_doc_path
if not origin_doc_path:
return api_error(status.HTTP_400_BAD_REQUEST, 'Origin file path is invalid.')
origin_file_version = revision.origin_file_version
if not origin_file_version:
return api_error(status.HTTP_400_BAD_REQUEST, 'Origin file version is missing.')
origin_doc_uuid = revision.origin_doc_uuid
origin_doc_uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(origin_doc_uuid)
if not origin_doc_uuid_map:
error_msg = 'Origin file uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
username = request.user.username
token = seafile_api.get_fileserver_access_token(origin_doc_uuid_map.repo_id,
origin_file_version, 'download', username)
if not token:
error_msg = 'Origin file %s not found.' % origin_doc_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
origin_file_name = os.path.basename(origin_doc_path)
download_url = gen_inner_file_get_url(token, origin_file_name)
resp = requests.get(download_url)
if not resp.ok:
logger.error(resp.text)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
return Response({
'content': resp.content
})
class SeadocPublishedRevisionContent(APIView):
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
throttle_classes = (UserRateThrottle,)
def get(self, request, file_uuid):
if not file_uuid:
error_msg = 'file_uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
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)
if not revision.is_published:
error_msg = 'Revision %s is not published.' % file_uuid
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
origin_doc_path = revision.origin_doc_path
if not origin_doc_path:
return api_error(status.HTTP_400_BAD_REQUEST, 'Origin file path is invalid.')
publish_file_version = revision.publish_file_version
if not publish_file_version:
return api_error(status.HTTP_400_BAD_REQUEST, 'Origin file version is missing.')
origin_doc_uuid = revision.origin_doc_uuid
origin_doc_uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(origin_doc_uuid)
if not origin_doc_uuid_map:
error_msg = 'Origin file uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
username = request.user.username
token = seafile_api.get_fileserver_access_token(origin_doc_uuid_map.repo_id,
publish_file_version, 'download', username)
if not token:
error_msg = 'Origin file %s not found.' % origin_doc_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
origin_file_name = os.path.basename(origin_doc_path)
download_url = gen_inner_file_get_url(token, origin_file_name)
resp = requests.get(download_url)
if not resp.ok:
logger.error(resp.text)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
return Response({
'content': resp.content
})
class SdocParticipantsView(APIView):
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
throttle_classes = (UserRateThrottle,)
def get(self, request, file_uuid):
"""List all participants of a file.
"""
if not file_uuid:
error_msg = 'file_uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
# permission check
if not check_folder_permission(request, repo_id, '/'):
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
# main
try:
participant_list = []
participant_queryset = FileParticipant.objects.get_participants(file_uuid)
for participant in participant_queryset:
participant_info = get_user_common_info(participant.username)
participant_list.append(participant_info)
except Exception as e:
logger.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
return Response({'participant_list': participant_list})
def post(self, request, file_uuid):
"""batch add participants of a file.
"""
# argument check
if not file_uuid:
error_msg = 'file_uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
# permission check
if not check_folder_permission(request, repo_id, '/'):
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
emails = request.data.get('emails')
if not emails or not isinstance(emails, list):
return api_error(status.HTTP_400_BAD_REQUEST, 'emails invalid.')
emails = list(set(emails))
# batch add
success = list()
failed = list()
try:
participants_queryset = FileParticipant.objects.get_participants(file_uuid)
except Exception as e:
logger.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
for email in emails:
if not is_valid_username(email):
error_dic = {'email': email, 'error_msg': 'email invalid.', 'error_code': 400}
failed.append(error_dic)
continue
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
error_dic = {'email': email, 'error_msg': 'User not found.', 'error_code': 404}
failed.append(error_dic)
continue
# permission check
if not seafile_api.check_permission_by_path(repo_id, '/', user.username):
error_dic = {'email': email, 'error_msg': _('Permission denied.'), 'error_code': 403}
failed.append(error_dic)
continue
# main
try:
if participants_queryset.filter(uuid=uuid_map, username=email).count() > 0:
error_dic = {'email': email, 'error_msg': _('The participant already exists.'), 'error_code': 409}
failed.append(error_dic)
continue
FileParticipant.objects.add_participant(uuid_map, email)
participant = get_user_common_info(email)
success.append(participant)
except Exception as e:
logger.error(e)
error_dic = {'email': email, 'error_msg': _('Internal Server Error'), 'error_code': 500}
failed.append(error_dic)
continue
username = request.user.username
sdoc_server_api = SdocServerAPI(file_uuid, str(uuid_map.filename), username)
try:
sdoc_server_api.add_participant(success)
except Exception as e:
logger.error('Sdoc server notification to add participants error', e)
return Response({'success': success, 'failed': failed})
class SdocParticipantView(APIView):
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
throttle_classes = (UserRateThrottle,)
def delete(self, request, file_uuid):
"""Delete a participant
"""
# argument check
if not file_uuid:
error_msg = 'file_uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
# permission check
if not check_folder_permission(request, repo_id, '/'):
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
email = request.data.get('email')
if not email or not is_valid_username(email):
return api_error(status.HTTP_400_BAD_REQUEST, 'email invalid.')
try:
if not FileParticipant.objects.get_participant(file_uuid, email):
return api_error(status.HTTP_404_NOT_FOUND, 'Participant %s not found.' % email)
FileParticipant.objects.delete_participant(file_uuid, email)
except Exception as e:
logger.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
username = request.user.username
sdoc_server_api = SdocServerAPI(file_uuid, str(uuid_map.filename), username)
try:
sdoc_server_api.remove_participant(email)
except Exception as e:
logger.error('Sdoc server notification to remove participants error', e)
return Response({'success': True})
class SdocRelatedUsers(APIView):
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
throttle_classes = (UserRateThrottle,)
def get(self, request, file_uuid):
if not file_uuid:
error_msg = 'file_uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
org_id = get_org_id_by_repo_id(repo_id)
related_users = []
try:
related_user_list = get_related_users_by_repo(repo_id, org_id)
db_api = CcnetDB()
user_obj_list = db_api.get_active_users_by_user_list(related_user_list)
for username in user_obj_list:
user_info = get_user_common_info(username)
related_users.append(user_info)
except Exception as e:
logger.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
return Response({'related_users': related_users})
class SeadocEditorCallBack(APIView):
authentication_classes = ()
throttle_classes = (UserRateThrottle,)
def post(self, request, file_uuid):
# jwt permission check
auth = request.headers.get('authorization', '').split()
if not is_valid_seadoc_access_token(auth, file_uuid):
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
# file info check
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
filetype, fileext = get_file_type_and_ext(uuid_map.filename)
if filetype != SEADOC:
error_msg = 'seadoc file type %s invalid.' % filetype
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# currently only implement unlock file
sdoc_status = request.POST.get('status', '')
if sdoc_status != 'no_write':
error_msg = 'status invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# unlock file
repo_id = uuid_map.repo_id
file_path = posixpath.join(uuid_map.parent_path, uuid_map.filename)
try:
if is_pro_version() and if_locked_by_online_office(repo_id, file_path):
seafile_api.unlock_file(repo_id, file_path)
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})
class SeadocSearchFilenameView(APIView):
authentication_classes = (SdocJWTTokenAuthentication, TokenAuthentication, SessionAuthentication)
permission_classes = (IsProVersion, )
throttle_classes = (UserRateThrottle,)
def get(self, request, file_uuid):
"""Search sdoc by filename.
"""
if not (HAS_FILE_SEARCH or HAS_FILE_SEASEARCH):
error_msg = 'Search not supported.'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# argument check
query = request.GET.get('query', None)
if not query:
error_msg = 'query invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
try:
current_page = int(request.GET.get('page', '1'))
per_page = int(request.GET.get('per_page', '10'))
if per_page > 100:
per_page = 100
except ValueError:
current_page = 1
per_page = 10
search_type = request.GET.get('search_type', None)
suffixes = []
if search_type == 'sdoc':
suffixes = ['sdoc',]
if search_type == 'file':
suffixes = get_non_sdoc_file_exts()
if not suffixes:
error_msg = 'search_type is not valid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# resource check
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo_id = uuid_map.repo_id
repo = seafile_api.get_repo(repo_id)
search_filename_only = True
if HAS_FILE_SEARCH:
org_id = get_org_id_by_repo_id(repo_id)
map_id = repo.origin_repo_id if repo.origin_repo_id else repo_id
repo_id_map = {map_id: repo}
keyword = query
start = (current_page - 1) * per_page
size = per_page
search_path = None
time_range = (None, None)
size_range = (None, None)
obj_type = 'file'
obj_desc = {
'obj_type': obj_type,
'suffixes': suffixes,
'time_range': time_range,
'size_range': size_range,
}
try:
results, total = search_files(
repo_id_map, search_path, keyword, obj_desc, start, size, org_id, search_filename_only)
except Exception as e:
logger.error(e)
results, total = [], 0
return Response({"total": total, "results": results, "has_more": False})
for f in results:
f.pop('repo', None)
f.pop('exists', None)
f.pop('last_modified_by', None)
f.pop('name_highlight', None)
f.pop('score', None)
f.pop('content_highlight', None)
f.pop('last_modified', None)
f.pop('repo_owner_contact_email', None)
f.pop('repo_owner_email', None)
f.pop('repo_owner_name', None)
f.pop('thumbnail_url', None)
f.pop('size', None)
f['doc_uuid'] = get_seadoc_file_uuid(repo, f['fullpath'])
has_more = True if total > current_page * per_page else False
return Response({"total":total, "results":results, "has_more":has_more})
elif HAS_FILE_SEASEARCH:
repos = [(repo.id, repo.origin_repo_id, repo.origin_path, repo.name)]
searched_repos, repos_map = format_repos(repos)
results, total = ai_search_files(query, searched_repos, per_page, suffixes)
for f in results:
repo_id = f['repo_id']
repo = repos_map.get(repo_id, None)
if not repo:
continue
real_repo_id = repo[0]
origin_path = repo[1]
repo_name = repo[2]
f['repo_name'] = repo_name
f.pop('_id', None)
if origin_path:
if not f['fullpath'].startswith(origin_path):
# this operation will reduce the result items, but it will not happen now
continue
else:
f['repo_id'] = real_repo_id
f['fullpath'] = f['fullpath'].split(origin_path)[-1]
f['doc_uuid'] = get_seadoc_file_uuid(repo, f['fullpath'])
return Response({"total": total, "results": results, "has_more": False})
class SeadocExportView(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
def get(self, request, file_uuid):
username = request.user.username
uuid_map = FileUUIDMap.objects.get_fileuuidmap_by_uuid(file_uuid)
if not uuid_map:
error_msg = 'seadoc uuid %s not found.' % file_uuid
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
filetype, fileext = get_file_type_and_ext(uuid_map.filename)
if filetype != SEADOC:
error_msg = 'seadoc file type %s invalid.' % filetype
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
# permission check
permission = check_folder_permission(request, uuid_map.repo_id, uuid_map.parent_path)
if not permission:
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
try:
tmp_zip_path = export_sdoc(uuid_map, username)
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
if not os.path.exists(tmp_zip_path):
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
response = FileResponse(open(tmp_zip_path, 'rb'), content_type="application/x-zip-compressed", as_attachment=True)
response['Content-Disposition'] = 'attachment;filename*=UTF-8\'\'' + quote(uuid_map.filename[:-4] + ZSDOC)
tmp_dir = os.path.join('/tmp/sdoc', str(uuid_map.uuid))
if os.path.exists(tmp_dir):
shutil.rmtree(tmp_dir)
return response
class SdocImportView(APIView):
authentication_classes = (TokenAuthentication, CsrfExemptSessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
def post(self, request, repo_id):
# use TemporaryFileUploadHandler, which contains TemporaryUploadedFile
# TemporaryUploadedFile has temporary_file_path() method
# in order to change upload_handlers, we must exempt csrf check
request.upload_handlers = [TemporaryFileUploadHandler(request=request)]
username = request.user.username
relative_path = request.data.get('relative_path', '/').strip('/')
parent_dir = request.data.get('parent_dir', '/')
replace = request.data.get('replace', 'False')
try:
replace = to_python_boolean(replace)
except ValueError:
error_msg = 'replace invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
file = request.FILES.get('file', None)
if not file:
error_msg = 'file can not be found.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
filename = file.name
uploaded_temp_path = file.temporary_file_path()
extension = filename.split('.')[-1].lower()
if not (extension == 'sdoczip' and is_zipfile(uploaded_temp_path)):
error_msg = 'file format not supported.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
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)
dir_id = seafile_api.get_dir_id_by_path(repo_id, parent_dir)
if not dir_id:
error_msg = 'Folder %s not found.' % parent_dir
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
if parse_repo_perm(check_folder_permission(request, repo_id, parent_dir)).can_upload is False:
return api_error(status.HTTP_403_FORBIDDEN, 'You do not have permission to access this folder.')
if check_quota(repo_id) < 0:
return api_error(HTTP_443_ABOVE_QUOTA, _("Out of quota."))
obj_id = json.dumps({'parent_dir': parent_dir})
try:
token = seafile_api.get_fileserver_access_token(repo_id, obj_id, 'upload', username, use_onetime=False)
except Exception as e:
if str(e) == 'Too many files in library.':
error_msg = _("The number of files in library exceeds the limit")
return api_error(HTTP_447_TOO_MANY_FILES_IN_LIBRARY, error_msg)
else:
logger.error(e)
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
if not token:
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
upload_link = gen_file_upload_url(token, 'upload-api')
upload_link += '?ret-json=1'
if replace:
upload_link += '&replace=1'
# upload file
tmp_dir = str(uuid.uuid4())
tmp_extracted_path = os.path.join('/tmp/seahub', str(repo_id), 'sdoc_zip_extracted/', tmp_dir)
try:
with ZipFile(uploaded_temp_path) as zip_file:
zip_file.extractall(tmp_extracted_path)
except Exception as e:
logger.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
sdoc_file_name = filename.replace(ZSDOC, 'sdoc')
new_file_path = os.path.join(parent_dir, relative_path, sdoc_file_name)
data = {'parent_dir': parent_dir, 'target_file': new_file_path, 'relative_path': relative_path}
if replace:
data['replace'] = 1
sdoc_file_path = os.path.join(tmp_extracted_path, 'content.json')
new_sdoc_file_path = os.path.join(tmp_extracted_path, sdoc_file_name)
os.rename(sdoc_file_path, new_sdoc_file_path)
files = {'file': open(new_sdoc_file_path, 'rb')}
resp = requests.post(upload_link, files=files, data=data)
if not resp.ok:
logger.error('save file: %s failed: %s' % (filename, resp.text))
return api_error(resp.status_code, resp.content)
sdoc_name = json.loads(resp.content)[0].get('name')
new_sdoc_file_path = os.path.join(parent_dir, relative_path, sdoc_name)
doc_uuid = get_seadoc_file_uuid(repo, new_sdoc_file_path)
# upload sdoc images
image_dir = os.path.join(tmp_extracted_path, 'images/')
if os.path.exists(image_dir):
batch_upload_sdoc_images(doc_uuid, repo_id, username, image_dir)
# remove tmp file
if os.path.exists(tmp_extracted_path):
shutil.rmtree(tmp_extracted_path)
return Response({'success': True})
def batch_upload_sdoc_images(doc_uuid, repo_id, username, image_dir):
parent_path = gen_seadoc_image_parent_path(doc_uuid, repo_id, username)
upload_link = get_seadoc_asset_upload_link(repo_id, parent_path, username)
file_list = os.listdir(image_dir)
for filename in file_list:
file_path = posixpath.join(parent_path, filename)
image_path = os.path.join(image_dir, filename)
image_file = open(image_path, 'rb')
files = {'file': image_file}
data = {'parent_dir': parent_path, 'filename': filename, 'target_file': file_path}
resp = requests.post(upload_link, files=files, data=data)
if not resp.ok:
logger.warning('upload sdoc image: %s failed: %s', filename, resp.text)