mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Setup backend permissions system
This commit is contained in:
parent
18c09ecd93
commit
4357fbf83f
77
rsconcept/backend/apps/rsform/permissions.py
Normal file
77
rsconcept/backend/apps/rsform/permissions.py
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
''' Custom Permission classes.
|
||||||
|
Hierarchy: Anonymous -> User -> Editor -> Owner -> Admin
|
||||||
|
'''
|
||||||
|
from typing import Any, cast
|
||||||
|
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from rest_framework.permissions import AllowAny as Anyone # pylint: disable=unused-import
|
||||||
|
from rest_framework.permissions import BasePermission as _Base
|
||||||
|
from rest_framework.permissions import \
|
||||||
|
IsAuthenticated as GlobalUser # pylint: disable=unused-import
|
||||||
|
from rest_framework.request import Request
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
|
from . import models as m
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_item(obj: Any) -> m.LibraryItem:
|
||||||
|
if isinstance(obj, m.LibraryItem):
|
||||||
|
return obj
|
||||||
|
elif isinstance(obj, m.Constituenta):
|
||||||
|
return cast(m.LibraryItem, obj.schema)
|
||||||
|
elif isinstance(obj, (m.Version, m.Subscription, m.Editor)):
|
||||||
|
return cast(m.LibraryItem, obj.item)
|
||||||
|
raise PermissionDenied({
|
||||||
|
'message': 'Invalid type error. Please contact developers',
|
||||||
|
'object_id': obj.id
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalAdmin(_Base):
|
||||||
|
''' Item permission: Admin or higher. '''
|
||||||
|
|
||||||
|
def has_permission(self, request: Request, view: APIView) -> bool:
|
||||||
|
if not hasattr(request.user, 'is_staff'):
|
||||||
|
return False
|
||||||
|
return request.user.is_staff # type: ignore
|
||||||
|
|
||||||
|
def has_object_permission(self, request: Request, view: APIView, obj: Any) -> bool:
|
||||||
|
if not hasattr(request.user, 'is_staff'):
|
||||||
|
return False
|
||||||
|
return request.user.is_staff # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
class ItemOwner(GlobalAdmin):
|
||||||
|
''' Item permission: Owner or higher. '''
|
||||||
|
|
||||||
|
def has_permission(self, request: Request, view: APIView) -> bool:
|
||||||
|
return not request.user.is_anonymous
|
||||||
|
|
||||||
|
def has_object_permission(self, request: Request, view: APIView, obj: Any) -> bool:
|
||||||
|
if request.user == _extract_item(obj).owner:
|
||||||
|
return True
|
||||||
|
return super().has_object_permission(request, view, obj)
|
||||||
|
|
||||||
|
|
||||||
|
class ItemEditor(ItemOwner):
|
||||||
|
''' Item permission: Editor or higher. '''
|
||||||
|
|
||||||
|
def has_object_permission(self, request: Request, view: APIView, obj: Any) -> bool:
|
||||||
|
if m.Editor.objects.filter(
|
||||||
|
item=_extract_item(obj),
|
||||||
|
editor=cast(m.User, request.user)
|
||||||
|
).exists():
|
||||||
|
return True
|
||||||
|
return super().has_object_permission(request, view, obj)
|
||||||
|
|
||||||
|
|
||||||
|
class EditorMixin(APIView):
|
||||||
|
''' Editor permissions mixin for API views. '''
|
||||||
|
|
||||||
|
def get_permissions(self):
|
||||||
|
result = super().get_permissions()
|
||||||
|
if self.request.method.upper() == 'GET':
|
||||||
|
result.append(Anyone())
|
||||||
|
else:
|
||||||
|
result.append(ItemEditor())
|
||||||
|
return result
|
|
@ -1,16 +1,16 @@
|
||||||
''' Testing API: Library. '''
|
''' Testing API: Library. '''
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
|
from apps.rsform.models import LibraryItem, LibraryItemType, LibraryTemplate, RSForm, Subscription
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
from apps.rsform.models import LibraryItem, LibraryItemType, Subscription, LibraryTemplate, RSForm
|
|
||||||
|
|
||||||
from ..testing_utils import response_contains
|
from ..testing_utils import response_contains
|
||||||
|
from .EndpointTester import EndpointTester, decl_endpoint
|
||||||
from .EndpointTester import decl_endpoint, EndpointTester
|
|
||||||
|
|
||||||
|
|
||||||
class TestLibraryViewset(EndpointTester):
|
class TestLibraryViewset(EndpointTester):
|
||||||
''' Testing Library view. '''
|
''' Testing Library view. '''
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.owned = LibraryItem.objects.create(
|
self.owned = LibraryItem.objects.create(
|
||||||
|
@ -71,30 +71,6 @@ class TestLibraryViewset(EndpointTester):
|
||||||
self.assertTrue(response.status_code in [status.HTTP_202_ACCEPTED, status.HTTP_204_NO_CONTENT])
|
self.assertTrue(response.status_code in [status.HTTP_202_ACCEPTED, status.HTTP_204_NO_CONTENT])
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/library/{item}/claim', method='post')
|
|
||||||
def test_claim(self):
|
|
||||||
self.assertNotFound(item=self.invalid_item)
|
|
||||||
self.assertForbidden(item=self.owned.id)
|
|
||||||
|
|
||||||
self.owned.is_common = True
|
|
||||||
self.owned.save()
|
|
||||||
self.assertNotModified(item=self.owned.id)
|
|
||||||
self.assertForbidden(item=self.unowned.id)
|
|
||||||
|
|
||||||
self.assertFalse(self.user in self.unowned.subscribers())
|
|
||||||
self.unowned.is_common = True
|
|
||||||
self.unowned.save()
|
|
||||||
|
|
||||||
self.assertOK(item=self.unowned.id)
|
|
||||||
self.unowned.refresh_from_db()
|
|
||||||
self.assertEqual(self.unowned.owner, self.user)
|
|
||||||
self.assertEqual(self.unowned.owner, self.user)
|
|
||||||
self.assertTrue(self.user in self.unowned.subscribers())
|
|
||||||
|
|
||||||
self.logout()
|
|
||||||
self.assertForbidden(item=self.owned.id)
|
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/library/active', method='get')
|
@decl_endpoint('/api/library/active', method='get')
|
||||||
def test_retrieve_common(self):
|
def test_retrieve_common(self):
|
||||||
response = self.execute()
|
response = self.execute()
|
||||||
|
|
|
@ -4,8 +4,6 @@ import re
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
from rest_framework.permissions import BasePermission, IsAuthenticated
|
|
||||||
|
|
||||||
# Name for JSON inside Exteor files archive
|
# Name for JSON inside Exteor files archive
|
||||||
EXTEOR_INNER_FILENAME = 'document.json'
|
EXTEOR_INNER_FILENAME = 'document.json'
|
||||||
|
|
||||||
|
@ -13,48 +11,6 @@ EXTEOR_INNER_FILENAME = 'document.json'
|
||||||
_REF_OLD_PATTERN = re.compile(r'@{([^0-9\-][^\}\|\{]*?)\|([^\}\|\{]*?)\|([^\}\|\{]*?)}')
|
_REF_OLD_PATTERN = re.compile(r'@{([^0-9\-][^\}\|\{]*?)\|([^\}\|\{]*?)\|([^\}\|\{]*?)}')
|
||||||
|
|
||||||
|
|
||||||
class ObjectOwnerOrAdmin(BasePermission):
|
|
||||||
''' Permission for object ownership restriction '''
|
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
|
||||||
if request.user == obj.owner:
|
|
||||||
return True
|
|
||||||
if not hasattr(request.user, 'is_staff'):
|
|
||||||
return False
|
|
||||||
return request.user.is_staff # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
class IsClaimable(IsAuthenticated):
|
|
||||||
''' Permission for object ownership restriction '''
|
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
|
||||||
if not super().has_permission(request, view):
|
|
||||||
return False
|
|
||||||
return obj.is_common
|
|
||||||
|
|
||||||
|
|
||||||
class SchemaOwnerOrAdmin(BasePermission):
|
|
||||||
''' Permission for object ownership restriction '''
|
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
|
||||||
if request.user == obj.schema.owner:
|
|
||||||
return True
|
|
||||||
if not hasattr(request.user, 'is_staff'):
|
|
||||||
return False
|
|
||||||
return request.user.is_staff # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
class ItemOwnerOrAdmin(BasePermission):
|
|
||||||
''' Permission for object ownership restriction '''
|
|
||||||
|
|
||||||
def has_object_permission(self, request, view, obj):
|
|
||||||
if request.user == obj.item.owner:
|
|
||||||
return True
|
|
||||||
if not hasattr(request.user, 'is_staff'):
|
|
||||||
return False
|
|
||||||
return request.user.is_staff # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
def read_zipped_json(data, json_filename: str) -> dict:
|
def read_zipped_json(data, json_filename: str) -> dict:
|
||||||
''' Read JSON from zipped data '''
|
''' Read JSON from zipped data '''
|
||||||
with ZipFile(data, 'r') as archive:
|
with ZipFile(data, 'r') as archive:
|
||||||
|
|
|
@ -1,23 +1,15 @@
|
||||||
''' Endpoints for Constituenta. '''
|
''' Endpoints for Constituenta. '''
|
||||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics
|
||||||
|
|
||||||
from .. import models as m
|
from .. import models as m
|
||||||
|
from .. import permissions
|
||||||
from .. import serializers as s
|
from .. import serializers as s
|
||||||
from .. import utils
|
|
||||||
|
|
||||||
|
|
||||||
@extend_schema(tags=['Constituenta'])
|
@extend_schema(tags=['Constituenta'])
|
||||||
@extend_schema_view()
|
@extend_schema_view()
|
||||||
class ConstituentAPIView(generics.RetrieveUpdateAPIView):
|
class ConstituentAPIView(generics.RetrieveUpdateAPIView, permissions.EditorMixin):
|
||||||
''' Endpoint: Get / Update Constituenta. '''
|
''' Endpoint: Get / Update Constituenta. '''
|
||||||
queryset = m.Constituenta.objects.all()
|
queryset = m.Constituenta.objects.all()
|
||||||
serializer_class = s.CstSerializer
|
serializer_class = s.CstSerializer
|
||||||
|
|
||||||
def get_permissions(self):
|
|
||||||
result = super().get_permissions()
|
|
||||||
if self.request.method.upper() == 'GET':
|
|
||||||
result.append(permissions.AllowAny())
|
|
||||||
else:
|
|
||||||
result.append(utils.SchemaOwnerOrAdmin())
|
|
||||||
return result
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.db import transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||||
from rest_framework import filters, generics, permissions
|
from rest_framework import filters, generics
|
||||||
from rest_framework import status as c
|
from rest_framework import status as c
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
@ -14,15 +14,15 @@ from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from .. import models as m
|
from .. import models as m
|
||||||
|
from .. import permissions
|
||||||
from .. import serializers as s
|
from .. import serializers as s
|
||||||
from .. import utils
|
|
||||||
|
|
||||||
|
|
||||||
@extend_schema(tags=['Library'])
|
@extend_schema(tags=['Library'])
|
||||||
@extend_schema_view()
|
@extend_schema_view()
|
||||||
class LibraryActiveView(generics.ListAPIView):
|
class LibraryActiveView(generics.ListAPIView):
|
||||||
''' Endpoint: Get list of library items available for active user. '''
|
''' Endpoint: Get list of library items available for active user. '''
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.Anyone,)
|
||||||
serializer_class = s.LibraryItemSerializer
|
serializer_class = s.LibraryItemSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
@ -40,7 +40,7 @@ class LibraryActiveView(generics.ListAPIView):
|
||||||
@extend_schema_view()
|
@extend_schema_view()
|
||||||
class LibraryAdminView(generics.ListAPIView):
|
class LibraryAdminView(generics.ListAPIView):
|
||||||
''' Endpoint: Get list of all library items. Admin only '''
|
''' Endpoint: Get list of all library items. Admin only '''
|
||||||
permission_classes = (permissions.IsAdminUser,)
|
permission_classes = (permissions.GlobalAdmin,)
|
||||||
serializer_class = s.LibraryItemSerializer
|
serializer_class = s.LibraryItemSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
@ -51,7 +51,7 @@ class LibraryAdminView(generics.ListAPIView):
|
||||||
@extend_schema_view()
|
@extend_schema_view()
|
||||||
class LibraryTemplatesView(generics.ListAPIView):
|
class LibraryTemplatesView(generics.ListAPIView):
|
||||||
''' Endpoint: Get list of templates. '''
|
''' Endpoint: Get list of templates. '''
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.Anyone,)
|
||||||
serializer_class = s.LibraryItemSerializer
|
serializer_class = s.LibraryItemSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
@ -79,14 +79,14 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
return serializer.save()
|
return serializer.save()
|
||||||
|
|
||||||
def get_permissions(self):
|
def get_permissions(self):
|
||||||
if self.action in ['update', 'destroy', 'partial_update']:
|
if self.action in ['destroy']:
|
||||||
permission_list = [utils.ObjectOwnerOrAdmin]
|
permission_list = [permissions.ItemOwner]
|
||||||
|
elif self.action in ['update', 'partial_update']:
|
||||||
|
permission_list = [permissions.ItemEditor]
|
||||||
elif self.action in ['create', 'clone', 'subscribe', 'unsubscribe']:
|
elif self.action in ['create', 'clone', 'subscribe', 'unsubscribe']:
|
||||||
permission_list = [permissions.IsAuthenticated]
|
permission_list = [permissions.GlobalUser]
|
||||||
elif self.action in ['claim']:
|
|
||||||
permission_list = [utils.IsClaimable]
|
|
||||||
else:
|
else:
|
||||||
permission_list = [permissions.AllowAny]
|
permission_list = [permissions.Anyone]
|
||||||
return [permission() for permission in permission_list]
|
return [permission() for permission in permission_list]
|
||||||
|
|
||||||
def _get_item(self) -> m.LibraryItem:
|
def _get_item(self) -> m.LibraryItem:
|
||||||
|
@ -134,32 +134,6 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
)
|
)
|
||||||
return Response(status=c.HTTP_400_BAD_REQUEST)
|
return Response(status=c.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
@extend_schema(
|
|
||||||
summary='claim item',
|
|
||||||
tags=['Library'],
|
|
||||||
request=None,
|
|
||||||
responses={
|
|
||||||
c.HTTP_200_OK: s.LibraryItemSerializer,
|
|
||||||
c.HTTP_403_FORBIDDEN: None,
|
|
||||||
c.HTTP_404_NOT_FOUND: None
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@transaction.atomic
|
|
||||||
@action(detail=True, methods=['post'])
|
|
||||||
def claim(self, request: Request, pk=None):
|
|
||||||
''' Endpoint: Claim ownership of LibraryItem. '''
|
|
||||||
item = self._get_item()
|
|
||||||
if item.owner == self.request.user:
|
|
||||||
return Response(status=c.HTTP_304_NOT_MODIFIED)
|
|
||||||
else:
|
|
||||||
item.owner = cast(m.User, self.request.user)
|
|
||||||
item.save()
|
|
||||||
m.Subscription.subscribe(user=item.owner, item=item)
|
|
||||||
return Response(
|
|
||||||
status=c.HTTP_200_OK,
|
|
||||||
data=s.LibraryItemSerializer(item).data
|
|
||||||
)
|
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
summary='subscribe to item',
|
summary='subscribe to item',
|
||||||
tags=['Library'],
|
tags=['Library'],
|
||||||
|
|
|
@ -6,7 +6,7 @@ import pyconcept
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics
|
||||||
from rest_framework import status as c
|
from rest_framework import status as c
|
||||||
from rest_framework import views, viewsets
|
from rest_framework import views, viewsets
|
||||||
from rest_framework.decorators import action, api_view
|
from rest_framework.decorators import action, api_view
|
||||||
|
@ -15,6 +15,7 @@ from rest_framework.response import Response
|
||||||
|
|
||||||
from .. import messages as msg
|
from .. import messages as msg
|
||||||
from .. import models as m
|
from .. import models as m
|
||||||
|
from .. import permissions
|
||||||
from .. import serializers as s
|
from .. import serializers as s
|
||||||
from .. import utils
|
from .. import utils
|
||||||
|
|
||||||
|
@ -33,9 +34,9 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
''' Determine permission class. '''
|
''' Determine permission class. '''
|
||||||
if self.action in ['load_trs', 'cst_create', 'cst_delete_multiple',
|
if self.action in ['load_trs', 'cst_create', 'cst_delete_multiple',
|
||||||
'reset_aliases', 'cst_rename', 'cst_substitute']:
|
'reset_aliases', 'cst_rename', 'cst_substitute']:
|
||||||
permission_list = [utils.ObjectOwnerOrAdmin]
|
permission_list = [permissions.ItemOwner]
|
||||||
else:
|
else:
|
||||||
permission_list = [permissions.AllowAny]
|
permission_list = [permissions.Anyone]
|
||||||
return [permission() for permission in permission_list]
|
return [permission() for permission in permission_list]
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
|
@ -402,7 +403,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
class TrsImportView(views.APIView):
|
class TrsImportView(views.APIView):
|
||||||
''' Endpoint: Upload RS form in Exteor format. '''
|
''' Endpoint: Upload RS form in Exteor format. '''
|
||||||
serializer_class = s.FileSerializer
|
serializer_class = s.FileSerializer
|
||||||
permission_classes = [permissions.IsAuthenticated]
|
permission_classes = [permissions.GlobalUser]
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
summary='import TRS file into RSForm',
|
summary='import TRS file into RSForm',
|
||||||
|
|
|
@ -3,7 +3,7 @@ from typing import cast
|
||||||
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||||
from rest_framework import generics, permissions
|
from rest_framework import generics
|
||||||
from rest_framework import status as c
|
from rest_framework import status as c
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework.decorators import action, api_view, permission_classes
|
from rest_framework.decorators import action, api_view, permission_classes
|
||||||
|
@ -11,25 +11,22 @@ from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from .. import models as m
|
from .. import models as m
|
||||||
|
from .. import permissions
|
||||||
from .. import serializers as s
|
from .. import serializers as s
|
||||||
from .. import utils
|
from .. import utils
|
||||||
|
|
||||||
|
|
||||||
@extend_schema(tags=['Version'])
|
@extend_schema(tags=['Version'])
|
||||||
@extend_schema_view()
|
@extend_schema_view()
|
||||||
class VersionViewset(viewsets.GenericViewSet, generics.RetrieveUpdateDestroyAPIView):
|
class VersionViewset(
|
||||||
|
viewsets.GenericViewSet,
|
||||||
|
generics.RetrieveUpdateDestroyAPIView,
|
||||||
|
permissions.EditorMixin
|
||||||
|
):
|
||||||
''' Endpoint: Get / Update Constituenta. '''
|
''' Endpoint: Get / Update Constituenta. '''
|
||||||
queryset = m.Version.objects.all()
|
queryset = m.Version.objects.all()
|
||||||
serializer_class = s.VersionSerializer
|
serializer_class = s.VersionSerializer
|
||||||
|
|
||||||
def get_permissions(self):
|
|
||||||
result = super().get_permissions()
|
|
||||||
if self.request.method.upper() == 'GET':
|
|
||||||
result.append(permissions.AllowAny())
|
|
||||||
else:
|
|
||||||
result.append(utils.ItemOwnerOrAdmin())
|
|
||||||
return result
|
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
summary='restore version data into current item',
|
summary='restore version data into current item',
|
||||||
request=None,
|
request=None,
|
||||||
|
@ -62,7 +59,7 @@ class VersionViewset(viewsets.GenericViewSet, generics.RetrieveUpdateDestroyAPIV
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@api_view(['POST'])
|
@api_view(['POST'])
|
||||||
@permission_classes([permissions.IsAuthenticated])
|
@permission_classes([permissions.GlobalUser])
|
||||||
def create_version(request: Request, pk_item: int):
|
def create_version(request: Request, pk_item: int):
|
||||||
''' Endpoint: Create new version for RSForm copying current content. '''
|
''' Endpoint: Create new version for RSForm copying current content. '''
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -246,13 +246,6 @@ export function deleteLibraryItem(target: string, request: FrontAction) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function postClaimLibraryItem(target: string, request: FrontPull<ILibraryItem>) {
|
|
||||||
AxiosPost({
|
|
||||||
endpoint: `/api/library/${target}/claim`,
|
|
||||||
request: request
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function postSubscribe(target: string, request: FrontAction) {
|
export function postSubscribe(target: string, request: FrontAction) {
|
||||||
AxiosPost({
|
AxiosPost({
|
||||||
endpoint: `/api/library/${target}/subscribe`,
|
endpoint: `/api/library/${target}/subscribe`,
|
||||||
|
|
|
@ -20,7 +20,6 @@ import {
|
||||||
patchSubstituteConstituents,
|
patchSubstituteConstituents,
|
||||||
patchUploadTRS,
|
patchUploadTRS,
|
||||||
patchVersion,
|
patchVersion,
|
||||||
postClaimLibraryItem,
|
|
||||||
postCreateVersion,
|
postCreateVersion,
|
||||||
postNewConstituenta,
|
postNewConstituenta,
|
||||||
postSubscribe
|
postSubscribe
|
||||||
|
@ -63,7 +62,6 @@ interface IRSFormContext {
|
||||||
isSubscribed: boolean;
|
isSubscribed: boolean;
|
||||||
|
|
||||||
update: (data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => void;
|
update: (data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => void;
|
||||||
claim: (callback?: DataCallback<ILibraryItem>) => void;
|
|
||||||
subscribe: (callback?: () => void) => void;
|
subscribe: (callback?: () => void) => void;
|
||||||
unsubscribe: (callback?: () => void) => void;
|
unsubscribe: (callback?: () => void) => void;
|
||||||
download: (callback: DataCallback<Blob>) => void;
|
download: (callback: DataCallback<Blob>) => void;
|
||||||
|
@ -180,29 +178,6 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps)
|
||||||
[schemaID, setError, setSchema, schema, library]
|
[schemaID, setError, setSchema, schema, library]
|
||||||
);
|
);
|
||||||
|
|
||||||
const claim = useCallback(
|
|
||||||
(callback?: DataCallback<ILibraryItem>) => {
|
|
||||||
if (!schema || !user) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setError(undefined);
|
|
||||||
postClaimLibraryItem(schemaID, {
|
|
||||||
showError: true,
|
|
||||||
setLoading: setProcessing,
|
|
||||||
onError: setError,
|
|
||||||
onSuccess: newData => {
|
|
||||||
setSchema(Object.assign(schema, newData));
|
|
||||||
library.localUpdateItem(newData);
|
|
||||||
if (!user.subscriptions.includes(newData.id)) {
|
|
||||||
user.subscriptions.push(newData.id);
|
|
||||||
}
|
|
||||||
if (callback) callback(newData);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[schemaID, setError, schema, user, setSchema, library]
|
|
||||||
);
|
|
||||||
|
|
||||||
const subscribe = useCallback(
|
const subscribe = useCallback(
|
||||||
(callback?: () => void) => {
|
(callback?: () => void) => {
|
||||||
if (!schema || !user) {
|
if (!schema || !user) {
|
||||||
|
@ -543,7 +518,6 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps)
|
||||||
update,
|
update,
|
||||||
download,
|
download,
|
||||||
upload,
|
upload,
|
||||||
claim,
|
|
||||||
restoreOrder,
|
restoreOrder,
|
||||||
resetAliases,
|
resetAliases,
|
||||||
produceStructure,
|
produceStructure,
|
||||||
|
|
|
@ -2,15 +2,7 @@
|
||||||
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import {
|
import { IconDestroy, IconDownload, IconFollow, IconFollowOff, IconSave, IconShare } from '@/components/Icons';
|
||||||
IconDestroy,
|
|
||||||
IconDownload,
|
|
||||||
IconFollow,
|
|
||||||
IconFollowOff,
|
|
||||||
IconOwner,
|
|
||||||
IconSave,
|
|
||||||
IconShare
|
|
||||||
} from '@/components/Icons';
|
|
||||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||||
import MiniButton from '@/components/ui/MiniButton';
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
import Overlay from '@/components/ui/Overlay';
|
import Overlay from '@/components/ui/Overlay';
|
||||||
|
@ -28,7 +20,7 @@ interface RSFormToolbarProps {
|
||||||
onDestroy: () => void;
|
onDestroy: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function RSFormToolbar({ modified, anonymous, subscribed, claimable, onSubmit, onDestroy }: RSFormToolbarProps) {
|
function RSFormToolbar({ modified, anonymous, subscribed, onSubmit, onDestroy }: RSFormToolbarProps) {
|
||||||
const controller = useRSEdit();
|
const controller = useRSEdit();
|
||||||
const canSave = useMemo(() => modified && !controller.isProcessing, [modified, controller.isProcessing]);
|
const canSave = useMemo(() => modified && !controller.isProcessing, [modified, controller.isProcessing]);
|
||||||
return (
|
return (
|
||||||
|
@ -65,14 +57,6 @@ function RSFormToolbar({ modified, anonymous, subscribed, claimable, onSubmit, o
|
||||||
onClick={controller.toggleSubscribe}
|
onClick={controller.toggleSubscribe}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{!anonymous && claimable ? (
|
|
||||||
<MiniButton
|
|
||||||
title='Стать владельцем'
|
|
||||||
icon={<IconOwner size='1.25rem' className='icon-green' />}
|
|
||||||
disabled={controller.isProcessing}
|
|
||||||
onClick={controller.claim}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{controller.isMutable ? (
|
{controller.isMutable ? (
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title='Удалить схему'
|
title='Удалить схему'
|
||||||
|
|
|
@ -80,7 +80,6 @@ interface IRSEditContext {
|
||||||
promptTemplate: () => void;
|
promptTemplate: () => void;
|
||||||
promptClone: () => void;
|
promptClone: () => void;
|
||||||
promptUpload: () => void;
|
promptUpload: () => void;
|
||||||
claim: () => void;
|
|
||||||
share: () => void;
|
share: () => void;
|
||||||
toggleSubscribe: () => void;
|
toggleSubscribe: () => void;
|
||||||
download: () => void;
|
download: () => void;
|
||||||
|
@ -488,13 +487,6 @@ export const RSEditState = ({
|
||||||
});
|
});
|
||||||
}, [model, isModified]);
|
}, [model, isModified]);
|
||||||
|
|
||||||
const claim = useCallback(() => {
|
|
||||||
if (!window.confirm('Вы уверены, что хотите стать владельцем данной схемы?')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
model.claim(() => toast.success('Вы стали владельцем схемы'));
|
|
||||||
}, [model]);
|
|
||||||
|
|
||||||
const share = useCallback(() => {
|
const share = useCallback(() => {
|
||||||
const currentRef = window.location.href;
|
const currentRef = window.location.href;
|
||||||
const url = currentRef.includes('?') ? currentRef + '&share' : currentRef + '?share';
|
const url = currentRef.includes('?') ? currentRef + '&share' : currentRef + '?share';
|
||||||
|
@ -547,7 +539,6 @@ export const RSEditState = ({
|
||||||
promptClone,
|
promptClone,
|
||||||
promptUpload: () => setShowUpload(true),
|
promptUpload: () => setShowUpload(true),
|
||||||
download,
|
download,
|
||||||
claim,
|
|
||||||
share,
|
share,
|
||||||
toggleSubscribe,
|
toggleSubscribe,
|
||||||
|
|
||||||
|
|
|
@ -52,11 +52,6 @@ function RSTabsMenu({ onDestroy }: RSTabsMenuProps) {
|
||||||
const editMenu = useDropdown();
|
const editMenu = useDropdown();
|
||||||
const accessMenu = useDropdown();
|
const accessMenu = useDropdown();
|
||||||
|
|
||||||
function handleClaimOwner() {
|
|
||||||
editMenu.hide();
|
|
||||||
controller.claim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
schemaMenu.hide();
|
schemaMenu.hide();
|
||||||
onDestroy();
|
onDestroy();
|
||||||
|
@ -140,14 +135,6 @@ function RSTabsMenu({ onDestroy }: RSTabsMenuProps) {
|
||||||
onClick={schemaMenu.toggle}
|
onClick={schemaMenu.toggle}
|
||||||
/>
|
/>
|
||||||
<Dropdown isOpen={schemaMenu.isOpen}>
|
<Dropdown isOpen={schemaMenu.isOpen}>
|
||||||
{user ? (
|
|
||||||
<DropdownButton
|
|
||||||
text={model.isOwned ? 'Вы — владелец' : 'Стать владельцем'}
|
|
||||||
icon={<IconOwner size='1rem' className='icon-green' />}
|
|
||||||
disabled={!model.isClaimable && !model.isOwned}
|
|
||||||
onClick={!model.isOwned && model.isClaimable ? handleClaimOwner : undefined}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Поделиться'
|
text='Поделиться'
|
||||||
icon={<IconShare size='1rem' className='icon-primary' />}
|
icon={<IconShare size='1rem' className='icon-primary' />}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user