mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-08-14 04:40:36 +03:00
F: Improve data validation for user inputs and backend serializers
This commit is contained in:
parent
9964b8df23
commit
ebd1bfbd2c
|
@ -2,6 +2,7 @@
|
|||
|
||||
from .basics import AccessPolicySerializer, LocationSerializer, RenameLocationSerializer
|
||||
from .data_access import (
|
||||
LibraryItemBaseNonStrictSerializer,
|
||||
LibraryItemBaseSerializer,
|
||||
LibraryItemCloneSerializer,
|
||||
LibraryItemDetailsSerializer,
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from shared import messages as msg
|
||||
from shared.serializers import StrictSerializer
|
||||
|
||||
from ..models import AccessPolicy, validate_location
|
||||
|
||||
|
||||
class LocationSerializer(serializers.Serializer):
|
||||
class LocationSerializer(StrictSerializer):
|
||||
''' Serializer: Item location. '''
|
||||
location = serializers.CharField(max_length=500)
|
||||
|
||||
|
@ -19,7 +20,7 @@ class LocationSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class RenameLocationSerializer(serializers.Serializer):
|
||||
class RenameLocationSerializer(StrictSerializer):
|
||||
''' Serializer: rename location. '''
|
||||
target = serializers.CharField(max_length=500)
|
||||
new_location = serializers.CharField(max_length=500)
|
||||
|
@ -37,7 +38,7 @@ class RenameLocationSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class AccessPolicySerializer(serializers.Serializer):
|
||||
class AccessPolicySerializer(StrictSerializer):
|
||||
''' Serializer: Constituenta renaming. '''
|
||||
access_policy = serializers.CharField()
|
||||
|
||||
|
|
|
@ -4,11 +4,13 @@ from rest_framework import serializers
|
|||
from rest_framework.serializers import PrimaryKeyRelatedField as PKField
|
||||
|
||||
from apps.rsform.models import Constituenta
|
||||
from shared import messages
|
||||
from shared.serializers import StrictModelSerializer, StrictSerializer
|
||||
|
||||
from ..models import LibraryItem, Version
|
||||
|
||||
|
||||
class LibraryItemBaseSerializer(serializers.ModelSerializer):
|
||||
class LibraryItemBaseSerializer(StrictModelSerializer):
|
||||
''' Serializer: LibraryItem entry full access. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
|
@ -17,7 +19,16 @@ class LibraryItemBaseSerializer(serializers.ModelSerializer):
|
|||
read_only_fields = ('id',)
|
||||
|
||||
|
||||
class LibraryItemReferenceSerializer(serializers.ModelSerializer):
|
||||
class LibraryItemBaseNonStrictSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: LibraryItem entry full access and no strict validation. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = LibraryItem
|
||||
fields = '__all__'
|
||||
read_only_fields = ('id',)
|
||||
|
||||
|
||||
class LibraryItemReferenceSerializer(StrictModelSerializer):
|
||||
''' Serializer: reference to LibraryItem. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
|
@ -25,7 +36,7 @@ class LibraryItemReferenceSerializer(serializers.ModelSerializer):
|
|||
fields = 'id', 'alias'
|
||||
|
||||
|
||||
class LibraryItemSerializer(serializers.ModelSerializer):
|
||||
class LibraryItemSerializer(StrictModelSerializer):
|
||||
''' Serializer: LibraryItem entry limited access. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
|
@ -34,17 +45,27 @@ class LibraryItemSerializer(serializers.ModelSerializer):
|
|||
read_only_fields = ('id', 'item_type', 'owner', 'location', 'access_policy')
|
||||
|
||||
|
||||
class LibraryItemCloneSerializer(serializers.ModelSerializer):
|
||||
class LibraryItemCloneSerializer(StrictSerializer):
|
||||
''' Serializer: LibraryItem cloning. '''
|
||||
items = PKField(many=True, required=False, queryset=Constituenta.objects.all().only('pk'))
|
||||
class ItemCloneData(StrictModelSerializer):
|
||||
''' Serialize: LibraryItem cloning data. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = LibraryItem
|
||||
exclude = ['id', 'item_type', 'owner', 'read_only']
|
||||
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = LibraryItem
|
||||
exclude = ['id', 'item_type', 'owner']
|
||||
items = PKField(many=True, queryset=Constituenta.objects.all().only('pk', 'schema_id'))
|
||||
item_data = ItemCloneData()
|
||||
|
||||
def validate_items(self, value):
|
||||
schema = self.context.get('schema')
|
||||
invalid = [item.pk for item in value if item.schema_id != schema.id]
|
||||
if invalid:
|
||||
raise serializers.ValidationError(messages.constituentsInvalid(invalid))
|
||||
return value
|
||||
|
||||
|
||||
class VersionSerializer(serializers.ModelSerializer):
|
||||
class VersionSerializer(StrictModelSerializer):
|
||||
''' Serializer: Version data. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
|
@ -53,7 +74,7 @@ class VersionSerializer(serializers.ModelSerializer):
|
|||
read_only_fields = ('id', 'item', 'time_create')
|
||||
|
||||
|
||||
class VersionInnerSerializer(serializers.ModelSerializer):
|
||||
class VersionInnerSerializer(StrictModelSerializer):
|
||||
''' Serializer: Version data for list of versions. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
|
@ -62,7 +83,7 @@ class VersionInnerSerializer(serializers.ModelSerializer):
|
|||
read_only_fields = ('id', 'item', 'time_create')
|
||||
|
||||
|
||||
class VersionCreateSerializer(serializers.ModelSerializer):
|
||||
class VersionCreateSerializer(StrictModelSerializer):
|
||||
''' Serializer: Version create data. '''
|
||||
items = PKField(many=True, required=False, default=None, queryset=Constituenta.objects.all().only('pk'))
|
||||
|
||||
|
@ -72,7 +93,7 @@ class VersionCreateSerializer(serializers.ModelSerializer):
|
|||
fields = 'version', 'description', 'items'
|
||||
|
||||
|
||||
class LibraryItemDetailsSerializer(serializers.ModelSerializer):
|
||||
class LibraryItemDetailsSerializer(StrictModelSerializer):
|
||||
''' Serializer: LibraryItem detailed data. '''
|
||||
editors = serializers.SerializerMethodField()
|
||||
versions = serializers.SerializerMethodField()
|
||||
|
@ -90,11 +111,11 @@ class LibraryItemDetailsSerializer(serializers.ModelSerializer):
|
|||
return [VersionInnerSerializer(item).data for item in instance.getQ_versions().order_by('pk')]
|
||||
|
||||
|
||||
class UserTargetSerializer(serializers.Serializer):
|
||||
class UserTargetSerializer(StrictSerializer):
|
||||
''' Serializer: Target single User. '''
|
||||
user = PKField(many=False, queryset=User.objects.all().only('pk'))
|
||||
|
||||
|
||||
class UsersListSerializer(serializers.Serializer):
|
||||
class UsersListSerializer(StrictSerializer):
|
||||
''' Serializer: List of Users. '''
|
||||
users = PKField(many=True, queryset=User.objects.all().only('pk'))
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
''' Utility serializers for REST API schema - SHOULD NOT BE ACCESSED DIRECTLY. '''
|
||||
from rest_framework import serializers
|
||||
|
||||
from shared.serializers import StrictSerializer
|
||||
|
||||
class NewVersionResponse(serializers.Serializer):
|
||||
|
||||
class NewVersionResponse(StrictSerializer):
|
||||
''' Serializer: Create version response. '''
|
||||
version = serializers.IntegerField()
|
||||
schema = serializers.JSONField()
|
||||
|
|
|
@ -345,13 +345,12 @@ class TestLibraryViewset(EndpointTester):
|
|||
term_resolved='люди'
|
||||
)
|
||||
|
||||
data = {'title': 'Title1337'}
|
||||
data = {'item_data': {'title': 'Title1337'}, 'items': []}
|
||||
self.executeNotFound(data=data, item=self.invalid_item)
|
||||
self.executeCreated(data=data, item=self.unowned.pk)
|
||||
|
||||
data = {'title': 'Title1338'}
|
||||
response = self.executeCreated(data=data, item=self.owned.pk)
|
||||
self.assertEqual(response.data['title'], data['title'])
|
||||
self.assertEqual(response.data['title'], data['item_data']['title'])
|
||||
self.assertEqual(len(response.data['items']), 2)
|
||||
self.assertEqual(response.data['items'][0]['alias'], x12.alias)
|
||||
self.assertEqual(response.data['items'][0]['term_raw'], x12.term_raw)
|
||||
|
@ -359,14 +358,14 @@ class TestLibraryViewset(EndpointTester):
|
|||
self.assertEqual(response.data['items'][1]['term_raw'], d2.term_raw)
|
||||
self.assertEqual(response.data['items'][1]['term_resolved'], d2.term_resolved)
|
||||
|
||||
data = {'title': 'Title1340', 'items': []}
|
||||
data = {'item_data': {'title': 'Title1340'}, 'items': []}
|
||||
response = self.executeCreated(data=data, item=self.owned.pk)
|
||||
self.assertEqual(response.data['title'], data['title'])
|
||||
self.assertEqual(response.data['title'], data['item_data']['title'])
|
||||
self.assertEqual(len(response.data['items']), 2)
|
||||
|
||||
data = {'title': 'Title1341', 'items': [x12.pk]}
|
||||
data = {'item_data': {'title': 'Title1341'}, 'items': [x12.pk]}
|
||||
response = self.executeCreated(data=data, item=self.owned.pk)
|
||||
self.assertEqual(response.data['title'], data['title'])
|
||||
self.assertEqual(response.data['title'], data['item_data']['title'])
|
||||
self.assertEqual(len(response.data['items']), 1)
|
||||
self.assertEqual(response.data['items'][0]['alias'], x12.alias)
|
||||
self.assertEqual(response.data['items'][0]['term_raw'], x12.term_raw)
|
||||
|
|
|
@ -151,22 +151,24 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
@action(detail=True, methods=['post'], url_path='clone')
|
||||
def clone(self, request: Request, pk) -> HttpResponse:
|
||||
''' Endpoint: Create deep copy of library item. '''
|
||||
serializer = s.LibraryItemCloneSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
item = self._get_item()
|
||||
if item.item_type != m.LibraryItemType.RSFORM:
|
||||
return Response(status=c.HTTP_400_BAD_REQUEST)
|
||||
|
||||
serializer = s.LibraryItemCloneSerializer(data=request.data, context={'schema': item})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
data = serializer.validated_data['item_data']
|
||||
clone = deepcopy(item)
|
||||
clone.pk = None
|
||||
clone.owner = cast(User, self.request.user)
|
||||
clone.title = serializer.validated_data['title']
|
||||
clone.alias = serializer.validated_data.get('alias', '')
|
||||
clone.description = serializer.validated_data.get('description', '')
|
||||
clone.visible = serializer.validated_data.get('visible', True)
|
||||
clone.title = data['title']
|
||||
clone.alias = data.get('alias', '')
|
||||
clone.description = data.get('description', '')
|
||||
clone.visible = data.get('visible', True)
|
||||
clone.read_only = False
|
||||
clone.access_policy = serializer.validated_data.get('access_policy', m.AccessPolicy.PUBLIC)
|
||||
clone.location = serializer.validated_data.get('location', m.LocationHead.USER)
|
||||
clone.access_policy = data.get('access_policy', m.AccessPolicy.PUBLIC)
|
||||
clone.location = data.get('location', m.LocationHead.USER)
|
||||
|
||||
with transaction.atomic():
|
||||
clone.save()
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
''' Basic serializers that do not interact with database. '''
|
||||
from rest_framework import serializers
|
||||
|
||||
from shared.serializers import StrictSerializer
|
||||
|
||||
class PositionSerializer(serializers.Serializer):
|
||||
|
||||
class PositionSerializer(StrictSerializer):
|
||||
''' Serializer: Position data. '''
|
||||
x = serializers.FloatField()
|
||||
y = serializers.FloatField()
|
||||
|
@ -10,7 +12,7 @@ class PositionSerializer(serializers.Serializer):
|
|||
height = serializers.FloatField()
|
||||
|
||||
|
||||
class NodeSerializer(serializers.Serializer):
|
||||
class NodeSerializer(StrictSerializer):
|
||||
''' Oss node serializer. '''
|
||||
nodeID = serializers.CharField()
|
||||
x = serializers.FloatField()
|
||||
|
@ -19,12 +21,12 @@ class NodeSerializer(serializers.Serializer):
|
|||
height = serializers.FloatField()
|
||||
|
||||
|
||||
class LayoutSerializer(serializers.Serializer):
|
||||
class LayoutSerializer(StrictSerializer):
|
||||
''' Serializer: Layout data. '''
|
||||
data = serializers.ListField(child=NodeSerializer()) # type: ignore
|
||||
|
||||
|
||||
class SubstitutionExSerializer(serializers.Serializer):
|
||||
class SubstitutionExSerializer(StrictSerializer):
|
||||
''' Serializer: Substitution extended data. '''
|
||||
operation = serializers.IntegerField()
|
||||
original = serializers.IntegerField()
|
||||
|
|
|
@ -11,12 +11,13 @@ from apps.library.serializers import LibraryItemDetailsSerializer
|
|||
from apps.rsform.models import Constituenta
|
||||
from apps.rsform.serializers import SubstitutionSerializerBase
|
||||
from shared import messages as msg
|
||||
from shared.serializers import StrictModelSerializer, StrictSerializer
|
||||
|
||||
from ..models import Argument, Block, Inheritance, Operation, OperationSchema, OperationType
|
||||
from .basics import NodeSerializer, PositionSerializer, SubstitutionExSerializer
|
||||
|
||||
|
||||
class OperationSerializer(serializers.ModelSerializer):
|
||||
class OperationSerializer(StrictModelSerializer):
|
||||
''' Serializer: Operation data. '''
|
||||
is_import = serializers.BooleanField(default=False, required=False)
|
||||
|
||||
|
@ -27,7 +28,7 @@ class OperationSerializer(serializers.ModelSerializer):
|
|||
read_only_fields = ('id', 'oss')
|
||||
|
||||
|
||||
class BlockSerializer(serializers.ModelSerializer):
|
||||
class BlockSerializer(StrictModelSerializer):
|
||||
''' Serializer: Block data. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
|
@ -36,7 +37,7 @@ class BlockSerializer(serializers.ModelSerializer):
|
|||
read_only_fields = ('id', 'oss')
|
||||
|
||||
|
||||
class ArgumentSerializer(serializers.ModelSerializer):
|
||||
class ArgumentSerializer(StrictModelSerializer):
|
||||
''' Serializer: Operation data. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
|
@ -44,9 +45,9 @@ class ArgumentSerializer(serializers.ModelSerializer):
|
|||
fields = ('operation', 'argument')
|
||||
|
||||
|
||||
class CreateBlockSerializer(serializers.Serializer):
|
||||
class CreateBlockSerializer(StrictSerializer):
|
||||
''' Serializer: Block creation. '''
|
||||
class BlockCreateData(serializers.ModelSerializer):
|
||||
class BlockCreateData(StrictModelSerializer):
|
||||
''' Serializer: Block creation data. '''
|
||||
|
||||
class Meta:
|
||||
|
@ -92,9 +93,9 @@ class CreateBlockSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class UpdateBlockSerializer(serializers.Serializer):
|
||||
class UpdateBlockSerializer(StrictSerializer):
|
||||
''' Serializer: Block update. '''
|
||||
class UpdateBlockData(serializers.ModelSerializer):
|
||||
class UpdateBlockData(StrictModelSerializer):
|
||||
''' Serializer: Block update data. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
|
@ -129,7 +130,7 @@ class UpdateBlockSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class DeleteBlockSerializer(serializers.Serializer):
|
||||
class DeleteBlockSerializer(StrictSerializer):
|
||||
''' Serializer: Delete block. '''
|
||||
layout = serializers.ListField(
|
||||
child=NodeSerializer()
|
||||
|
@ -146,7 +147,7 @@ class DeleteBlockSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class MoveItemsSerializer(serializers.Serializer):
|
||||
class MoveItemsSerializer(StrictSerializer):
|
||||
''' Serializer: Move items to another parent. '''
|
||||
layout = serializers.ListField(
|
||||
child=NodeSerializer()
|
||||
|
@ -190,7 +191,7 @@ class MoveItemsSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class CreateOperationData(serializers.ModelSerializer):
|
||||
class CreateOperationData(StrictModelSerializer):
|
||||
''' Serializer: Operation creation data. '''
|
||||
alias = serializers.CharField()
|
||||
|
||||
|
@ -200,7 +201,7 @@ class CreateOperationData(serializers.ModelSerializer):
|
|||
fields = 'alias', 'title', 'description', 'parent'
|
||||
|
||||
|
||||
class CreateSchemaSerializer(serializers.Serializer):
|
||||
class CreateSchemaSerializer(StrictSerializer):
|
||||
''' Serializer: Schema creation for new operation. '''
|
||||
layout = serializers.ListField(child=NodeSerializer())
|
||||
item_data = CreateOperationData()
|
||||
|
@ -216,7 +217,7 @@ class CreateSchemaSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class ImportSchemaSerializer(serializers.Serializer):
|
||||
class ImportSchemaSerializer(StrictSerializer):
|
||||
''' Serializer: Import schema to new operation. '''
|
||||
layout = serializers.ListField(child=NodeSerializer())
|
||||
item_data = CreateOperationData()
|
||||
|
@ -238,7 +239,7 @@ class ImportSchemaSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class CreateSynthesisSerializer(serializers.Serializer):
|
||||
class CreateSynthesisSerializer(StrictSerializer):
|
||||
''' Serializer: Synthesis operation creation. '''
|
||||
layout = serializers.ListField(child=NodeSerializer())
|
||||
item_data = CreateOperationData()
|
||||
|
@ -292,9 +293,9 @@ class CreateSynthesisSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class UpdateOperationSerializer(serializers.Serializer):
|
||||
class UpdateOperationSerializer(StrictSerializer):
|
||||
''' Serializer: Operation update. '''
|
||||
class UpdateOperationData(serializers.ModelSerializer):
|
||||
class UpdateOperationData(StrictModelSerializer):
|
||||
''' Serializer: Operation update data. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
|
@ -369,7 +370,7 @@ class UpdateOperationSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class DeleteOperationSerializer(serializers.Serializer):
|
||||
class DeleteOperationSerializer(StrictSerializer):
|
||||
''' Serializer: Delete operation. '''
|
||||
layout = serializers.ListField(
|
||||
child=NodeSerializer()
|
||||
|
@ -388,7 +389,7 @@ class DeleteOperationSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class TargetOperationSerializer(serializers.Serializer):
|
||||
class TargetOperationSerializer(StrictSerializer):
|
||||
''' Serializer: Target single operation. '''
|
||||
layout = serializers.ListField(
|
||||
child=NodeSerializer()
|
||||
|
@ -405,7 +406,7 @@ class TargetOperationSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class SetOperationInputSerializer(serializers.Serializer):
|
||||
class SetOperationInputSerializer(StrictSerializer):
|
||||
''' Serializer: Set input schema for operation. '''
|
||||
layout = serializers.ListField(
|
||||
child=NodeSerializer()
|
||||
|
@ -432,7 +433,7 @@ class SetOperationInputSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class OperationSchemaSerializer(serializers.ModelSerializer):
|
||||
class OperationSchemaSerializer(StrictModelSerializer):
|
||||
''' Serializer: Detailed data for OSS. '''
|
||||
operations = serializers.ListField(
|
||||
child=OperationSerializer()
|
||||
|
@ -489,7 +490,7 @@ class OperationSchemaSerializer(serializers.ModelSerializer):
|
|||
return result
|
||||
|
||||
|
||||
class RelocateConstituentsSerializer(serializers.Serializer):
|
||||
class RelocateConstituentsSerializer(StrictSerializer):
|
||||
''' Serializer: Relocate constituents. '''
|
||||
destination = PKField(
|
||||
many=False,
|
||||
|
|
|
@ -2,29 +2,30 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from apps.library.serializers import LibraryItemSerializer
|
||||
from shared.serializers import StrictSerializer
|
||||
|
||||
from .data_access import OperationSchemaSerializer
|
||||
|
||||
|
||||
class OperationCreatedResponse(serializers.Serializer):
|
||||
class OperationCreatedResponse(StrictSerializer):
|
||||
''' Serializer: Create operation response. '''
|
||||
new_operation = serializers.IntegerField()
|
||||
oss = OperationSchemaSerializer()
|
||||
|
||||
|
||||
class BlockCreatedResponse(serializers.Serializer):
|
||||
class BlockCreatedResponse(StrictSerializer):
|
||||
''' Serializer: Create block response. '''
|
||||
new_block = serializers.IntegerField()
|
||||
oss = OperationSchemaSerializer()
|
||||
|
||||
|
||||
class SchemaCreatedResponse(serializers.Serializer):
|
||||
class SchemaCreatedResponse(StrictSerializer):
|
||||
''' Serializer: Create RSForm for input operation response. '''
|
||||
new_schema = LibraryItemSerializer()
|
||||
oss = OperationSchemaSerializer()
|
||||
|
||||
|
||||
class ConstituentaReferenceResponse(serializers.Serializer):
|
||||
class ConstituentaReferenceResponse(StrictSerializer):
|
||||
''' Serializer: Constituenta reference. '''
|
||||
id = serializers.IntegerField()
|
||||
schema = serializers.IntegerField()
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from shared import messages as msg
|
||||
from shared.serializers import StrictModelSerializer
|
||||
|
||||
from ..models import PromptTemplate
|
||||
|
||||
|
||||
class PromptTemplateSerializer(serializers.ModelSerializer):
|
||||
class PromptTemplateSerializer(StrictModelSerializer):
|
||||
'''Serializer for PromptTemplate, enforcing permissions and ownership logic.'''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
|
@ -42,7 +43,7 @@ class PromptTemplateSerializer(serializers.ModelSerializer):
|
|||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class PromptTemplateListSerializer(serializers.ModelSerializer):
|
||||
class PromptTemplateListSerializer(StrictModelSerializer):
|
||||
'''Serializer for listing PromptTemplates without the 'text' field.'''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
|
|
|
@ -4,26 +4,28 @@ from typing import cast
|
|||
from cctext import EntityReference, Reference, ReferenceType, Resolver, SyntacticReference
|
||||
from rest_framework import serializers
|
||||
|
||||
from shared.serializers import StrictSerializer
|
||||
|
||||
class ExpressionSerializer(serializers.Serializer):
|
||||
|
||||
class ExpressionSerializer(StrictSerializer):
|
||||
''' Serializer: RSLang expression. '''
|
||||
expression = serializers.CharField()
|
||||
|
||||
|
||||
class ConstituentaCheckSerializer(serializers.Serializer):
|
||||
class ConstituentaCheckSerializer(StrictSerializer):
|
||||
''' Serializer: RSLang expression. '''
|
||||
alias = serializers.CharField()
|
||||
definition_formal = serializers.CharField(allow_blank=True)
|
||||
cst_type = serializers.CharField()
|
||||
|
||||
|
||||
class WordFormSerializer(serializers.Serializer):
|
||||
class WordFormSerializer(StrictSerializer):
|
||||
''' Serializer: inflect request. '''
|
||||
text = serializers.CharField()
|
||||
grams = serializers.CharField()
|
||||
|
||||
|
||||
class MultiFormSerializer(serializers.Serializer):
|
||||
class MultiFormSerializer(StrictSerializer):
|
||||
''' Serializer: inflect request. '''
|
||||
items = serializers.ListField(
|
||||
child=WordFormSerializer()
|
||||
|
@ -41,18 +43,18 @@ class MultiFormSerializer(serializers.Serializer):
|
|||
return result
|
||||
|
||||
|
||||
class TextSerializer(serializers.Serializer):
|
||||
class TextSerializer(StrictSerializer):
|
||||
''' Serializer: Text with references. '''
|
||||
text = serializers.CharField()
|
||||
|
||||
|
||||
class FunctionArgSerializer(serializers.Serializer):
|
||||
class FunctionArgSerializer(StrictSerializer):
|
||||
''' Serializer: RSLang function argument type. '''
|
||||
alias = serializers.CharField()
|
||||
typification = serializers.CharField()
|
||||
|
||||
|
||||
class CstParseSerializer(serializers.Serializer):
|
||||
class CstParseSerializer(StrictSerializer):
|
||||
''' Serializer: Constituenta parse result. '''
|
||||
status = serializers.CharField()
|
||||
valueClass = serializers.CharField()
|
||||
|
@ -63,7 +65,7 @@ class CstParseSerializer(serializers.Serializer):
|
|||
)
|
||||
|
||||
|
||||
class ErrorDescriptionSerializer(serializers.Serializer):
|
||||
class ErrorDescriptionSerializer(StrictSerializer):
|
||||
''' Serializer: RSError description. '''
|
||||
errorType = serializers.IntegerField()
|
||||
position = serializers.IntegerField()
|
||||
|
@ -73,13 +75,13 @@ class ErrorDescriptionSerializer(serializers.Serializer):
|
|||
)
|
||||
|
||||
|
||||
class NodeDataSerializer(serializers.Serializer):
|
||||
class NodeDataSerializer(StrictSerializer):
|
||||
''' Serializer: Node data. '''
|
||||
dataType = serializers.CharField()
|
||||
value = serializers.CharField()
|
||||
|
||||
|
||||
class ASTNodeSerializer(serializers.Serializer):
|
||||
class ASTNodeSerializer(StrictSerializer):
|
||||
''' Serializer: Syntax tree node. '''
|
||||
uid = serializers.IntegerField()
|
||||
parent = serializers.IntegerField() # type: ignore
|
||||
|
@ -89,7 +91,7 @@ class ASTNodeSerializer(serializers.Serializer):
|
|||
data = NodeDataSerializer() # type: ignore
|
||||
|
||||
|
||||
class ExpressionParseSerializer(serializers.Serializer):
|
||||
class ExpressionParseSerializer(StrictSerializer):
|
||||
''' Serializer: RSlang expression parse result. '''
|
||||
parseResult = serializers.BooleanField()
|
||||
prefixLen = serializers.IntegerField()
|
||||
|
@ -108,13 +110,13 @@ class ExpressionParseSerializer(serializers.Serializer):
|
|||
)
|
||||
|
||||
|
||||
class TextPositionSerializer(serializers.Serializer):
|
||||
class TextPositionSerializer(StrictSerializer):
|
||||
''' Serializer: Text position. '''
|
||||
start = serializers.IntegerField()
|
||||
finish = serializers.IntegerField()
|
||||
|
||||
|
||||
class ReferenceDataSerializer(serializers.Serializer):
|
||||
class ReferenceDataSerializer(StrictSerializer):
|
||||
''' Serializer: Reference data - Union of all references. '''
|
||||
offset = serializers.IntegerField()
|
||||
nominal = serializers.CharField()
|
||||
|
@ -122,7 +124,7 @@ class ReferenceDataSerializer(serializers.Serializer):
|
|||
form = serializers.CharField()
|
||||
|
||||
|
||||
class ReferenceSerializer(serializers.Serializer):
|
||||
class ReferenceSerializer(StrictSerializer):
|
||||
''' Serializer: Language reference. '''
|
||||
type = serializers.CharField()
|
||||
data = ReferenceDataSerializer() # type: ignore
|
||||
|
@ -130,7 +132,7 @@ class ReferenceSerializer(serializers.Serializer):
|
|||
pos_output = TextPositionSerializer()
|
||||
|
||||
|
||||
class InheritanceDataSerializer(serializers.Serializer):
|
||||
class InheritanceDataSerializer(StrictSerializer):
|
||||
''' Serializer: inheritance data. '''
|
||||
child = serializers.IntegerField()
|
||||
child_source = serializers.IntegerField()
|
||||
|
@ -138,7 +140,7 @@ class InheritanceDataSerializer(serializers.Serializer):
|
|||
parent_source = serializers.IntegerField()
|
||||
|
||||
|
||||
class ResolverSerializer(serializers.Serializer):
|
||||
class ResolverSerializer(StrictSerializer):
|
||||
''' Serializer: Resolver results serializer. '''
|
||||
input = serializers.CharField()
|
||||
output = serializers.CharField()
|
||||
|
|
|
@ -9,19 +9,20 @@ from rest_framework.serializers import PrimaryKeyRelatedField as PKField
|
|||
|
||||
from apps.library.models import LibraryItem
|
||||
from apps.library.serializers import (
|
||||
LibraryItemBaseSerializer,
|
||||
LibraryItemBaseNonStrictSerializer,
|
||||
LibraryItemDetailsSerializer,
|
||||
LibraryItemReferenceSerializer
|
||||
)
|
||||
from apps.oss.models import Inheritance
|
||||
from shared import messages as msg
|
||||
from shared.serializers import StrictModelSerializer, StrictSerializer
|
||||
|
||||
from ..models import Constituenta, CstType, RSForm
|
||||
from .basics import CstParseSerializer, InheritanceDataSerializer
|
||||
from .io_pyconcept import PyConceptAdapter
|
||||
|
||||
|
||||
class CstBaseSerializer(serializers.ModelSerializer):
|
||||
class CstBaseSerializer(StrictModelSerializer):
|
||||
''' Serializer: Constituenta all data. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
|
@ -30,7 +31,7 @@ class CstBaseSerializer(serializers.ModelSerializer):
|
|||
read_only_fields = ('id',)
|
||||
|
||||
|
||||
class CstInfoSerializer(serializers.ModelSerializer):
|
||||
class CstInfoSerializer(StrictModelSerializer):
|
||||
''' Serializer: Constituenta public information. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
|
@ -38,9 +39,9 @@ class CstInfoSerializer(serializers.ModelSerializer):
|
|||
exclude = ('order', 'schema')
|
||||
|
||||
|
||||
class CstUpdateSerializer(serializers.Serializer):
|
||||
class CstUpdateSerializer(StrictSerializer):
|
||||
''' Serializer: Constituenta update. '''
|
||||
class ConstituentaUpdateData(serializers.ModelSerializer):
|
||||
class ConstituentaUpdateData(StrictModelSerializer):
|
||||
''' Serializer: Operation creation data. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
|
@ -70,7 +71,7 @@ class CstUpdateSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class CstDetailsSerializer(serializers.ModelSerializer):
|
||||
class CstDetailsSerializer(StrictModelSerializer):
|
||||
''' Serializer: Constituenta data including parse. '''
|
||||
parse = CstParseSerializer()
|
||||
|
||||
|
@ -80,7 +81,7 @@ class CstDetailsSerializer(serializers.ModelSerializer):
|
|||
exclude = ('order',)
|
||||
|
||||
|
||||
class CstCreateSerializer(serializers.ModelSerializer):
|
||||
class CstCreateSerializer(StrictModelSerializer):
|
||||
''' Serializer: Constituenta creation. '''
|
||||
insert_after = PKField(
|
||||
many=False,
|
||||
|
@ -100,7 +101,7 @@ class CstCreateSerializer(serializers.ModelSerializer):
|
|||
'insert_after', 'term_forms'
|
||||
|
||||
|
||||
class RSFormSerializer(serializers.ModelSerializer):
|
||||
class RSFormSerializer(StrictModelSerializer):
|
||||
''' Serializer: Detailed data for RSForm. '''
|
||||
editors = serializers.ListField(
|
||||
child=serializers.IntegerField()
|
||||
|
@ -208,7 +209,7 @@ class RSFormSerializer(serializers.ModelSerializer):
|
|||
validated_data=new_cst.validated_data
|
||||
)
|
||||
|
||||
loaded_item = LibraryItemBaseSerializer(data=data)
|
||||
loaded_item = LibraryItemBaseNonStrictSerializer(data=data)
|
||||
loaded_item.is_valid(raise_exception=True)
|
||||
loaded_item.update(
|
||||
instance=cast(LibraryItem, self.instance),
|
||||
|
@ -216,7 +217,7 @@ class RSFormSerializer(serializers.ModelSerializer):
|
|||
)
|
||||
|
||||
|
||||
class RSFormParseSerializer(serializers.ModelSerializer):
|
||||
class RSFormParseSerializer(StrictModelSerializer):
|
||||
''' Serializer: Detailed data for RSForm including parse. '''
|
||||
editors = serializers.ListField(
|
||||
child=serializers.IntegerField()
|
||||
|
@ -250,7 +251,7 @@ class RSFormParseSerializer(serializers.ModelSerializer):
|
|||
return data
|
||||
|
||||
|
||||
class CstTargetSerializer(serializers.Serializer):
|
||||
class CstTargetSerializer(StrictSerializer):
|
||||
''' Serializer: Target single Constituenta. '''
|
||||
target = PKField(many=False, queryset=Constituenta.objects.all())
|
||||
|
||||
|
@ -265,7 +266,7 @@ class CstTargetSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class CstListSerializer(serializers.Serializer):
|
||||
class CstListSerializer(StrictSerializer):
|
||||
''' Serializer: List of constituents from one origin. '''
|
||||
items = PKField(many=True, queryset=Constituenta.objects.all().only('schema_id'))
|
||||
|
||||
|
@ -287,13 +288,13 @@ class CstMoveSerializer(CstListSerializer):
|
|||
move_to = serializers.IntegerField()
|
||||
|
||||
|
||||
class SubstitutionSerializerBase(serializers.Serializer):
|
||||
class SubstitutionSerializerBase(StrictSerializer):
|
||||
''' Serializer: Basic substitution. '''
|
||||
original = PKField(many=False, queryset=Constituenta.objects.only('alias', 'schema_id'))
|
||||
substitution = PKField(many=False, queryset=Constituenta.objects.only('alias', 'schema_id'))
|
||||
|
||||
|
||||
class CstSubstituteSerializer(serializers.Serializer):
|
||||
class CstSubstituteSerializer(StrictSerializer):
|
||||
''' Serializer: Constituenta substitution. '''
|
||||
substitutions = serializers.ListField(
|
||||
child=SubstitutionSerializerBase(),
|
||||
|
@ -326,7 +327,7 @@ class CstSubstituteSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class InlineSynthesisSerializer(serializers.Serializer):
|
||||
class InlineSynthesisSerializer(StrictSerializer):
|
||||
''' Serializer: Inline synthesis operation input. '''
|
||||
receiver = PKField(many=False, queryset=LibraryItem.objects.all().only('owner_id'))
|
||||
source = PKField(many=False, queryset=LibraryItem.objects.all().only('owner_id')) # type: ignore
|
||||
|
|
|
@ -4,6 +4,7 @@ from rest_framework import serializers
|
|||
|
||||
from apps.library.models import LibraryItem
|
||||
from shared import messages as msg
|
||||
from shared.serializers import StrictSerializer
|
||||
|
||||
from ..models import Constituenta, RSForm
|
||||
from ..utils import fix_old_references
|
||||
|
@ -15,12 +16,12 @@ _TRS_VERSION = 16
|
|||
_TRS_HEADER = 'Exteor 4.8.13.1000 - 30/05/2022'
|
||||
|
||||
|
||||
class FileSerializer(serializers.Serializer):
|
||||
class FileSerializer(StrictSerializer):
|
||||
''' Serializer: File input. '''
|
||||
file = serializers.FileField(allow_empty_file=False)
|
||||
|
||||
|
||||
class RSFormUploadSerializer(serializers.Serializer):
|
||||
class RSFormUploadSerializer(StrictSerializer):
|
||||
''' Upload data for RSForm serializer. '''
|
||||
file = serializers.FileField()
|
||||
load_metadata = serializers.BooleanField()
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
''' Utility serializers for REST API schema - SHOULD NOT BE ACCESSED DIRECTLY. '''
|
||||
from rest_framework import serializers
|
||||
|
||||
from shared.serializers import StrictSerializer
|
||||
|
||||
from .data_access import RSFormParseSerializer
|
||||
|
||||
|
||||
class ResultTextResponse(serializers.Serializer):
|
||||
class ResultTextResponse(StrictSerializer):
|
||||
''' Serializer: Text result of a function call. '''
|
||||
result = serializers.CharField()
|
||||
|
||||
|
||||
class NewCstResponse(serializers.Serializer):
|
||||
class NewCstResponse(StrictSerializer):
|
||||
''' Serializer: Create cst response. '''
|
||||
new_cst = serializers.IntegerField()
|
||||
schema = RSFormParseSerializer()
|
||||
|
||||
|
||||
class NewMultiCstResponse(serializers.Serializer):
|
||||
class NewMultiCstResponse(StrictSerializer):
|
||||
''' Serializer: Create multiple cst response. '''
|
||||
cst_list = serializers.ListField(
|
||||
child=serializers.IntegerField()
|
||||
|
|
|
@ -5,18 +5,19 @@ from rest_framework import serializers
|
|||
|
||||
from apps.library.models import Editor
|
||||
from shared import messages as msg
|
||||
from shared.serializers import StrictModelSerializer, StrictSerializer
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
class NonFieldErrorSerializer(serializers.Serializer):
|
||||
class NonFieldErrorSerializer(StrictSerializer):
|
||||
''' Serializer: list of non-field errors. '''
|
||||
non_field_errors = serializers.ListField(
|
||||
child=serializers.CharField()
|
||||
)
|
||||
|
||||
|
||||
class LoginSerializer(serializers.Serializer):
|
||||
class LoginSerializer(StrictSerializer):
|
||||
''' Serializer: User authentication by login/password. '''
|
||||
username = serializers.CharField(
|
||||
label='Имя пользователя',
|
||||
|
@ -54,7 +55,7 @@ class LoginSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class AuthSerializer(serializers.Serializer):
|
||||
class AuthSerializer(StrictSerializer):
|
||||
''' Serializer: Authorization data. '''
|
||||
id = serializers.IntegerField()
|
||||
username = serializers.CharField()
|
||||
|
@ -77,7 +78,7 @@ class AuthSerializer(serializers.Serializer):
|
|||
}
|
||||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
class UserSerializer(StrictModelSerializer):
|
||||
''' Serializer: User data. '''
|
||||
id = serializers.IntegerField(read_only=True)
|
||||
|
||||
|
@ -105,7 +106,7 @@ class UserSerializer(serializers.ModelSerializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class UserInfoSerializer(serializers.ModelSerializer):
|
||||
class UserInfoSerializer(StrictModelSerializer):
|
||||
''' Serializer: User open information. '''
|
||||
id = serializers.IntegerField(read_only=True)
|
||||
|
||||
|
@ -119,13 +120,13 @@ class UserInfoSerializer(serializers.ModelSerializer):
|
|||
]
|
||||
|
||||
|
||||
class ChangePasswordSerializer(serializers.Serializer):
|
||||
class ChangePasswordSerializer(StrictSerializer):
|
||||
''' Serializer: Change password. '''
|
||||
old_password = serializers.CharField(required=True)
|
||||
new_password = serializers.CharField(required=True)
|
||||
|
||||
|
||||
class SignupSerializer(serializers.ModelSerializer):
|
||||
class SignupSerializer(StrictModelSerializer):
|
||||
''' Serializer: Create user profile. '''
|
||||
id = serializers.IntegerField(read_only=True)
|
||||
password = serializers.CharField(write_only=True, required=True, validators=[validate_password])
|
||||
|
|
|
@ -2,6 +2,14 @@
|
|||
# pylint: skip-file
|
||||
|
||||
|
||||
def fieldNotAllowed():
|
||||
return 'Недопустимое поле'
|
||||
|
||||
|
||||
def constituentsInvalid(constituents: list[int]):
|
||||
return f'некорректные конституенты для схемы: {constituents}'
|
||||
|
||||
|
||||
def constituentaNotInRSform(title: str):
|
||||
return f'Конституента не принадлежит схеме: {title}'
|
||||
|
||||
|
|
23
rsconcept/backend/shared/serializers.py
Normal file
23
rsconcept/backend/shared/serializers.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
import shared.messages as msg
|
||||
|
||||
|
||||
class StrictSerializer(serializers.Serializer):
|
||||
def to_internal_value(self, data):
|
||||
extra_keys = set(data.keys()) - set(self.fields.keys())
|
||||
if extra_keys:
|
||||
raise serializers.ValidationError({
|
||||
key: msg.fieldNotAllowed() for key in extra_keys
|
||||
})
|
||||
return super().to_internal_value(data)
|
||||
|
||||
|
||||
class StrictModelSerializer(serializers.ModelSerializer):
|
||||
def to_internal_value(self, data):
|
||||
extra_keys = set(data.keys()) - set(self.fields.keys())
|
||||
if extra_keys:
|
||||
raise serializers.ValidationError({
|
||||
key: msg.fieldNotAllowed() for key in extra_keys
|
||||
})
|
||||
return super().to_internal_value(data)
|
|
@ -1,5 +1,8 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import { limits } from '@/utils/constants';
|
||||
import { errorMsg } from '@/utils/labels';
|
||||
|
||||
/** Represents AI prompt. */
|
||||
export type IPromptTemplate = IPromptTemplateDTO;
|
||||
|
||||
|
@ -28,20 +31,22 @@ export const schemaPromptTemplate = z.strictObject({
|
|||
is_shared: z.boolean()
|
||||
});
|
||||
|
||||
export const schemaCreatePromptTemplate = schemaPromptTemplate.pick({
|
||||
label: true,
|
||||
description: true,
|
||||
text: true,
|
||||
is_shared: true
|
||||
const schemaPromptTemplateInput = schemaPromptTemplate
|
||||
.pick({
|
||||
is_shared: true,
|
||||
owner: true
|
||||
})
|
||||
.extend({
|
||||
label: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField),
|
||||
description: z.string().max(limits.len_description, errorMsg.descriptionLength),
|
||||
text: z.string().max(limits.len_text, errorMsg.textLength)
|
||||
});
|
||||
|
||||
export const schemaCreatePromptTemplate = schemaPromptTemplateInput.omit({
|
||||
owner: true
|
||||
});
|
||||
|
||||
export const schemaUpdatePromptTemplate = schemaPromptTemplate.pick({
|
||||
owner: true,
|
||||
label: true,
|
||||
description: true,
|
||||
text: true,
|
||||
is_shared: true
|
||||
});
|
||||
export const schemaUpdatePromptTemplate = schemaPromptTemplateInput;
|
||||
|
||||
export const schemaPromptTemplateInfo = schemaPromptTemplate.pick({
|
||||
id: true,
|
||||
|
|
|
@ -22,17 +22,23 @@ export function DlgCreatePromptTemplate() {
|
|||
const { items: templates } = useAvailableTemplatesSuspense();
|
||||
const { user } = useAuthSuspense();
|
||||
|
||||
const { handleSubmit, control, register } = useForm<ICreatePromptTemplateDTO>({
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
register,
|
||||
formState: { errors }
|
||||
} = useForm<ICreatePromptTemplateDTO>({
|
||||
resolver: zodResolver(schemaCreatePromptTemplate),
|
||||
defaultValues: {
|
||||
label: '',
|
||||
description: '',
|
||||
text: '',
|
||||
is_shared: false
|
||||
}
|
||||
},
|
||||
mode: 'onChange'
|
||||
});
|
||||
const label = useWatch({ control, name: 'label' });
|
||||
const isValid = label !== '' && !templates.find(template => template.label === label);
|
||||
const isValid = !!label && !templates.find(template => template.label === label);
|
||||
|
||||
function onSubmit(data: ICreatePromptTemplateDTO) {
|
||||
void createPromptTemplate(data).then(onCreate);
|
||||
|
@ -47,8 +53,8 @@ export function DlgCreatePromptTemplate() {
|
|||
submitInvalidTooltip='Введите уникальное название шаблона'
|
||||
className='cc-column w-140 max-h-120 py-2 px-6'
|
||||
>
|
||||
<TextInput id='dlg_prompt_label' {...register('label')} label='Название шаблона' />
|
||||
<TextArea id='dlg_prompt_description' {...register('description')} label='Описание' />
|
||||
<TextInput id='dlg_prompt_label' {...register('label')} label='Название шаблона' error={errors.label} />
|
||||
<TextArea id='dlg_prompt_description' {...register('description')} label='Описание' error={errors.description} />
|
||||
{user.is_staff ? (
|
||||
<Controller
|
||||
name='is_shared'
|
||||
|
|
|
@ -7,9 +7,6 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
|||
import clsx from 'clsx';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
|
||||
import { useMutatingPrompts } from '@/features/ai/backend/use-mutating-prompts';
|
||||
import { useUpdatePromptTemplate } from '@/features/ai/backend/use-update-prompt-template';
|
||||
import { generateSample } from '@/features/ai/models/prompting-api';
|
||||
import { useAuthSuspense } from '@/features/auth';
|
||||
|
||||
import { MiniButton } from '@/components/control';
|
||||
|
@ -24,6 +21,9 @@ import {
|
|||
type IUpdatePromptTemplateDTO,
|
||||
schemaUpdatePromptTemplate
|
||||
} from '../../../backend/types';
|
||||
import { useMutatingPrompts } from '../../../backend/use-mutating-prompts';
|
||||
import { useUpdatePromptTemplate } from '../../../backend/use-update-prompt-template';
|
||||
import { generateSample } from '../../../models/prompting-api';
|
||||
|
||||
interface FormPromptTemplateProps {
|
||||
promptTemplate: IPromptTemplate;
|
||||
|
@ -55,7 +55,8 @@ export function FormPromptTemplate({ promptTemplate, className, isMutable, toggl
|
|||
description: promptTemplate.description,
|
||||
text: promptTemplate.text,
|
||||
is_shared: promptTemplate.is_shared
|
||||
}
|
||||
},
|
||||
mode: 'onChange'
|
||||
});
|
||||
const text = useWatch({ control, name: 'text' });
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import { limits } from '@/utils/constants';
|
||||
import { errorMsg } from '@/utils/labels';
|
||||
|
||||
/**
|
||||
* Represents CurrentUser information.
|
||||
*/
|
||||
/** Represents CurrentUser information. */
|
||||
export interface ICurrentUser {
|
||||
id: number | null;
|
||||
username: string;
|
||||
|
@ -12,27 +11,40 @@ export interface ICurrentUser {
|
|||
editor: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents login data, used to authenticate users.
|
||||
*/
|
||||
export const schemaUserLogin = z.strictObject({
|
||||
username: z.string().nonempty(errorMsg.requiredField),
|
||||
password: z.string().nonempty(errorMsg.requiredField)
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents login data, used to authenticate users.
|
||||
*/
|
||||
/** Represents login data, used to authenticate users. */
|
||||
export type IUserLoginDTO = z.infer<typeof schemaUserLogin>;
|
||||
|
||||
/**
|
||||
* Represents data needed to update password for current user.
|
||||
*/
|
||||
/** Represents data needed to update password for current user. */
|
||||
export type IChangePasswordDTO = z.infer<typeof schemaChangePassword>;
|
||||
|
||||
/** Represents password reset request data. */
|
||||
export interface IRequestPasswordDTO {
|
||||
email: string;
|
||||
}
|
||||
|
||||
/** Represents password reset data. */
|
||||
export interface IResetPasswordDTO {
|
||||
password: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
/** Represents password token data. */
|
||||
export interface IPasswordTokenDTO {
|
||||
token: string;
|
||||
}
|
||||
|
||||
// ========= SCHEMAS ========
|
||||
|
||||
export const schemaUserLogin = z.strictObject({
|
||||
username: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField),
|
||||
password: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField)
|
||||
});
|
||||
|
||||
export const schemaChangePassword = z
|
||||
.object({
|
||||
old_password: z.string().nonempty(errorMsg.requiredField),
|
||||
new_password: z.string().nonempty(errorMsg.requiredField),
|
||||
new_password2: z.string().nonempty(errorMsg.requiredField)
|
||||
old_password: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField),
|
||||
new_password: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField),
|
||||
new_password2: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField)
|
||||
})
|
||||
.refine(schema => schema.new_password === schema.new_password2, {
|
||||
path: ['new_password2'],
|
||||
|
@ -42,30 +54,3 @@ export const schemaChangePassword = z
|
|||
path: ['new_password'],
|
||||
message: errorMsg.passwordsSame
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents data needed to update password for current user.
|
||||
*/
|
||||
export type IChangePasswordDTO = z.infer<typeof schemaChangePassword>;
|
||||
|
||||
/**
|
||||
* Represents password reset request data.
|
||||
*/
|
||||
export interface IRequestPasswordDTO {
|
||||
email: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents password reset data.
|
||||
*/
|
||||
export interface IResetPasswordDTO {
|
||||
password: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents password token data.
|
||||
*/
|
||||
export interface IPasswordTokenDTO {
|
||||
token: string;
|
||||
}
|
||||
|
|
|
@ -118,10 +118,10 @@ export const libraryApi = {
|
|||
successMessage: infoMsg.itemDestroyed
|
||||
}
|
||||
}),
|
||||
cloneItem: (data: ICloneLibraryItemDTO) =>
|
||||
cloneItem: ({ itemID, data }: { itemID: number; data: ICloneLibraryItemDTO }) =>
|
||||
axiosPost<ICloneLibraryItemDTO, IRSFormDTO>({
|
||||
schema: schemaRSForm,
|
||||
endpoint: `/api/library/${data.id}/clone`,
|
||||
endpoint: `/api/library/${itemID}/clone`,
|
||||
request: {
|
||||
data: data,
|
||||
successMessage: newSchema => infoMsg.cloneComplete(newSchema.alias)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import { limits } from '@/utils/constants';
|
||||
import { errorMsg } from '@/utils/labels';
|
||||
|
||||
import { validateLocation } from '../models/library-api';
|
||||
|
@ -61,9 +62,11 @@ export const schemaAccessPolicy = z.enum(Object.values(AccessPolicy) as [AccessP
|
|||
export const schemaLibraryItem = z.strictObject({
|
||||
id: z.coerce.number(),
|
||||
item_type: schemaLibraryItemType,
|
||||
title: z.string(),
|
||||
|
||||
alias: z.string().nonempty(),
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
|
||||
visible: z.boolean(),
|
||||
read_only: z.boolean(),
|
||||
location: z.string(),
|
||||
|
@ -76,58 +79,51 @@ export const schemaLibraryItem = z.strictObject({
|
|||
|
||||
export const schemaLibraryItemArray = z.array(schemaLibraryItem);
|
||||
|
||||
export const schemaCloneLibraryItem = schemaLibraryItem
|
||||
const schemaInputLibraryItem = schemaLibraryItem
|
||||
.pick({
|
||||
id: true,
|
||||
item_type: true,
|
||||
title: true,
|
||||
alias: true,
|
||||
description: true,
|
||||
visible: true,
|
||||
read_only: true,
|
||||
location: true,
|
||||
access_policy: true
|
||||
})
|
||||
.extend({
|
||||
title: z.string().nonempty(errorMsg.requiredField),
|
||||
alias: z.string().nonempty(errorMsg.requiredField),
|
||||
location: z.string().refine(data => validateLocation(data), { message: errorMsg.invalidLocation }),
|
||||
|
||||
items: z.array(z.number())
|
||||
alias: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField),
|
||||
title: z.string().max(limits.len_title, errorMsg.titleLength).nonempty(errorMsg.requiredField),
|
||||
description: z.string().max(limits.len_description, errorMsg.descriptionLength),
|
||||
location: z.string().refine(data => validateLocation(data), { message: errorMsg.invalidLocation })
|
||||
});
|
||||
|
||||
export const schemaCreateLibraryItem = z
|
||||
.object({
|
||||
item_type: schemaLibraryItemType,
|
||||
title: z.string().optional(),
|
||||
alias: z.string().optional(),
|
||||
description: z.string(),
|
||||
visible: z.boolean(),
|
||||
read_only: z.boolean(),
|
||||
location: z.string().refine(data => validateLocation(data), { message: errorMsg.invalidLocation }),
|
||||
access_policy: schemaAccessPolicy,
|
||||
export const schemaCloneLibraryItem = z.strictObject({
|
||||
items: z.array(z.number()),
|
||||
item_data: schemaInputLibraryItem.omit({ item_type: true, read_only: true })
|
||||
});
|
||||
|
||||
export const schemaCreateLibraryItem = schemaInputLibraryItem
|
||||
.extend({
|
||||
alias: z.string().max(limits.len_alias, errorMsg.aliasLength).optional(),
|
||||
title: z.string().max(limits.len_title, errorMsg.titleLength).optional(),
|
||||
description: z.string().max(limits.len_description, errorMsg.descriptionLength).optional(),
|
||||
|
||||
file: z.instanceof(File).optional(),
|
||||
fileName: z.string().optional()
|
||||
})
|
||||
.refine(data => !!data.file || !!data.title, {
|
||||
path: ['title'],
|
||||
message: errorMsg.requiredField
|
||||
})
|
||||
.refine(data => !!data.file || !!data.alias, {
|
||||
path: ['alias'],
|
||||
message: errorMsg.requiredField
|
||||
})
|
||||
.refine(data => !!data.file || !!data.title, {
|
||||
path: ['title'],
|
||||
message: errorMsg.requiredField
|
||||
});
|
||||
|
||||
export const schemaUpdateLibraryItem = z.strictObject({
|
||||
id: z.number(),
|
||||
item_type: schemaLibraryItemType,
|
||||
title: z.string().nonempty(errorMsg.requiredField),
|
||||
alias: z.string().nonempty(errorMsg.requiredField),
|
||||
description: z.string(),
|
||||
visible: z.boolean(),
|
||||
read_only: z.boolean()
|
||||
});
|
||||
export const schemaUpdateLibraryItem = schemaInputLibraryItem
|
||||
.omit({
|
||||
location: true,
|
||||
access_policy: true
|
||||
})
|
||||
.extend({
|
||||
id: z.number()
|
||||
});
|
||||
|
||||
export const schemaVersionInfo = z.strictObject({
|
||||
id: z.coerce.number(),
|
||||
|
@ -136,18 +132,19 @@ export const schemaVersionInfo = z.strictObject({
|
|||
time_create: z.string().datetime({ offset: true })
|
||||
});
|
||||
|
||||
const schemaVersionInput = z.strictObject({
|
||||
version: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField),
|
||||
description: z.string().max(limits.len_description, errorMsg.descriptionLength)
|
||||
});
|
||||
|
||||
export const schemaVersionExInfo = schemaVersionInfo.extend({
|
||||
item: z.number()
|
||||
});
|
||||
|
||||
export const schemaUpdateVersion = z.strictObject({
|
||||
id: z.number(),
|
||||
version: z.string().nonempty(errorMsg.requiredField),
|
||||
description: z.string()
|
||||
export const schemaUpdateVersion = schemaVersionInput.extend({
|
||||
id: z.number()
|
||||
});
|
||||
|
||||
export const schemaCreateVersion = z.strictObject({
|
||||
version: z.string(),
|
||||
description: z.string(),
|
||||
export const schemaCreateVersion = schemaVersionInput.extend({
|
||||
items: z.array(z.number())
|
||||
});
|
||||
|
|
|
@ -14,6 +14,6 @@ export const useCloneItem = () => {
|
|||
onError: () => client.invalidateQueries()
|
||||
});
|
||||
return {
|
||||
cloneItem: (data: ICloneLibraryItemDTO) => mutation.mutateAsync(data)
|
||||
cloneItem: (data: { itemID: number; data: ICloneLibraryItemDTO }) => mutation.mutateAsync(data)
|
||||
};
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@ import clsx from 'clsx';
|
|||
|
||||
import { useAuthSuspense } from '@/features/auth';
|
||||
|
||||
import { TextArea } from '@/components/input';
|
||||
import { ErrorField, TextArea } from '@/components/input';
|
||||
import { type Styling } from '@/components/props';
|
||||
|
||||
import { LocationHead } from '../../models/library';
|
||||
|
@ -56,8 +56,8 @@ export function PickLocation({
|
|||
rows={rows}
|
||||
value={value.substring(3)}
|
||||
onChange={event => onChange(combineLocation(value.substring(0, 2), event.target.value))}
|
||||
error={error}
|
||||
/>
|
||||
<ErrorField className='absolute bottom-1 right-4' error={error} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ export function DlgChangeLocation() {
|
|||
overflowVisible
|
||||
header='Изменение расположения'
|
||||
submitText='Переместить'
|
||||
submitInvalidTooltip={`Допустимы буквы, цифры, подчерк, пробел и "!". Сегмент пути не может начинаться и заканчиваться пробелом. Общая длина (с корнем) не должна превышать ${limits.location_len}`}
|
||||
submitInvalidTooltip={`Допустимы буквы, цифры, подчерк, пробел и "!". Сегмент пути не может начинаться и заканчиваться пробелом. Общая длина (с корнем) не должна превышать ${limits.len_location}`}
|
||||
canSubmit={isValid && isDirty}
|
||||
onSubmit={event => void handleSubmit(onSubmit)(event)}
|
||||
className='w-130 pb-3 px-6 h-36'
|
||||
|
|
|
@ -39,15 +39,14 @@ export function DlgCloneLibraryItem() {
|
|||
} = useForm<ICloneLibraryItemDTO>({
|
||||
resolver: zodResolver(schemaCloneLibraryItem),
|
||||
defaultValues: {
|
||||
id: base.id,
|
||||
item_type: base.item_type,
|
||||
title: cloneTitle(base),
|
||||
alias: base.alias,
|
||||
description: base.description,
|
||||
visible: true,
|
||||
read_only: false,
|
||||
access_policy: AccessPolicy.PUBLIC,
|
||||
location: initialLocation,
|
||||
item_data: {
|
||||
title: cloneTitle(base),
|
||||
alias: base.alias,
|
||||
description: base.description,
|
||||
visible: true,
|
||||
access_policy: AccessPolicy.PUBLIC,
|
||||
location: initialLocation
|
||||
},
|
||||
items: []
|
||||
},
|
||||
mode: 'onChange',
|
||||
|
@ -55,7 +54,10 @@ export function DlgCloneLibraryItem() {
|
|||
});
|
||||
|
||||
function onSubmit(data: ICloneLibraryItemDTO) {
|
||||
return cloneItem(data).then(newSchema => router.pushAsync({ path: urls.schema(newSchema.id), force: true }));
|
||||
return cloneItem({
|
||||
itemID: base.id,
|
||||
data: data
|
||||
}).then(newSchema => router.pushAsync({ path: urls.schema(newSchema.id), force: true }));
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -69,18 +71,24 @@ export function DlgCloneLibraryItem() {
|
|||
<TextInput
|
||||
id='dlg_full_name' //
|
||||
label='Название'
|
||||
{...register('title')}
|
||||
error={errors.title}
|
||||
{...register('item_data.title')}
|
||||
error={errors.item_data?.title}
|
||||
/>
|
||||
|
||||
<div className='flex justify-between gap-3'>
|
||||
<TextInput id='dlg_alias' label='Сокращение' className='w-64' {...register('alias')} error={errors.alias} />
|
||||
<TextInput
|
||||
id='dlg_alias'
|
||||
label='Сокращение'
|
||||
className='w-64'
|
||||
{...register('item_data.alias')}
|
||||
error={errors.item_data?.alias}
|
||||
/>
|
||||
<div className='flex flex-col gap-2'>
|
||||
<Label text='Доступ' className='self-center select-none' />
|
||||
<div className='ml-auto cc-icons'>
|
||||
<Controller
|
||||
control={control}
|
||||
name='access_policy'
|
||||
name='item_data.access_policy'
|
||||
render={({ field }) => (
|
||||
<SelectAccessPolicy
|
||||
value={field.value} //
|
||||
|
@ -91,7 +99,7 @@ export function DlgCloneLibraryItem() {
|
|||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name='visible'
|
||||
name='item_data.visible'
|
||||
render={({ field }) => (
|
||||
<MiniButton
|
||||
title={field.value ? 'Библиотека: отображать' : 'Библиотека: скрывать'}
|
||||
|
@ -107,19 +115,24 @@ export function DlgCloneLibraryItem() {
|
|||
|
||||
<Controller
|
||||
control={control}
|
||||
name='location'
|
||||
name='item_data.location'
|
||||
render={({ field }) => (
|
||||
<PickLocation
|
||||
value={field.value} //
|
||||
rows={2}
|
||||
onChange={field.onChange}
|
||||
className={!!errors.location ? '-mb-6' : undefined}
|
||||
error={errors.location}
|
||||
error={errors.item_data?.location}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<TextArea id='dlg_comment' {...register('description')} label='Описание' rows={4} error={errors.description} />
|
||||
<TextArea
|
||||
id='dlg_comment'
|
||||
{...register('item_data.description')}
|
||||
label='Описание'
|
||||
rows={4}
|
||||
error={errors.item_data?.description}
|
||||
/>
|
||||
|
||||
{selected.length > 0 ? (
|
||||
<Controller
|
||||
|
|
|
@ -26,16 +26,22 @@ export function DlgCreateVersion() {
|
|||
);
|
||||
const { createVersion: versionCreate } = useCreateVersion();
|
||||
|
||||
const { register, handleSubmit, control } = useForm<ICreateVersionDTO>({
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
control,
|
||||
formState: { errors }
|
||||
} = useForm<ICreateVersionDTO>({
|
||||
resolver: zodResolver(schemaCreateVersion),
|
||||
defaultValues: {
|
||||
version: versions.length > 0 ? nextVersion(versions[versions.length - 1].version) : '1.0.0',
|
||||
description: '',
|
||||
items: []
|
||||
}
|
||||
},
|
||||
mode: 'onChange'
|
||||
});
|
||||
const version = useWatch({ control, name: 'version' });
|
||||
const canSubmit = !versions.find(ver => ver.version === version);
|
||||
const canSubmit = !!version && !versions.find(ver => ver.version === version);
|
||||
|
||||
function onSubmit(data: ICreateVersionDTO) {
|
||||
return versionCreate({ itemID, data }).then(onCreate);
|
||||
|
@ -50,7 +56,7 @@ export function DlgCreateVersion() {
|
|||
submitText='Создать'
|
||||
onSubmit={event => void handleSubmit(onSubmit)(event)}
|
||||
>
|
||||
<TextInput id='dlg_version' {...register('version')} dense label='Версия' className='w-64' />
|
||||
<TextInput id='dlg_version' {...register('version')} label='Версия' className='w-64' error={errors.version} />
|
||||
<TextArea id='dlg_description' {...register('description')} spellCheck label='Описание' rows={3} />
|
||||
{selected.length > 0 ? (
|
||||
<Controller
|
||||
|
|
|
@ -53,7 +53,7 @@ export function DlgEditVersions() {
|
|||
const versionName = useWatch({ control, name: 'version' });
|
||||
|
||||
const isValid = useMemo(
|
||||
() => schema.versions.every(ver => ver.id === versionID || ver.version != versionName),
|
||||
() => !!versionName && schema.versions.every(ver => ver.id === versionID || ver.version != versionName),
|
||||
[schema, versionID, versionName]
|
||||
);
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ export function nextVersion(version: string): string {
|
|||
* Validation location against regexp.
|
||||
*/
|
||||
export function validateLocation(location: string): boolean {
|
||||
return location.length <= limits.location_len && LOCATION_REGEXP.test(location);
|
||||
return location.length <= limits.len_location && LOCATION_REGEXP.test(location);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,7 +8,6 @@ import { urls, useConceptNavigation } from '@/app';
|
|||
|
||||
import { Button, MiniButton, SubmitButton } from '@/components/control';
|
||||
import { IconDownload } from '@/components/icons';
|
||||
import { InfoError } from '@/components/info-error';
|
||||
import { Label, TextArea, TextInput } from '@/components/input';
|
||||
import { EXTEOR_TRS_FILE } from '@/utils/constants';
|
||||
|
||||
|
@ -28,7 +27,7 @@ import { useLibrarySearchStore } from '../../stores/library-search';
|
|||
|
||||
export function FormCreateItem() {
|
||||
const router = useConceptNavigation();
|
||||
const { createItem, isPending, error: serverError, reset: clearServerError } = useCreateItem();
|
||||
const { createItem, isPending, reset: clearServerError } = useCreateItem();
|
||||
|
||||
const searchLocation = useLibrarySearchStore(state => state.location);
|
||||
const setSearchLocation = useLibrarySearchStore(state => state.setLocation);
|
||||
|
@ -203,7 +202,6 @@ export function FormCreateItem() {
|
|||
value={field.value} //
|
||||
rows={2}
|
||||
onChange={field.onChange}
|
||||
className={!!errors.location ? '-mb-6' : undefined}
|
||||
error={errors.location}
|
||||
/>
|
||||
)}
|
||||
|
@ -221,7 +219,6 @@ export function FormCreateItem() {
|
|||
<SubmitButton text='Создать схему' loading={isPending} className='min-w-40' />
|
||||
<Button text='Отмена' className='min-w-40' onClick={() => handleCancel()} />
|
||||
</div>
|
||||
{serverError ? <InfoError error={serverError} /> : null}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { z } from 'zod';
|
|||
import { schemaLibraryItem } from '@/features/library/backend/types';
|
||||
import { schemaSubstituteConstituents } from '@/features/rsform/backend/types';
|
||||
|
||||
import { limits } from '@/utils/constants';
|
||||
import { errorMsg } from '@/utils/labels';
|
||||
|
||||
/** Represents {@link IOperation} type. */
|
||||
|
@ -92,11 +93,18 @@ export const schemaOperation = z.strictObject({
|
|||
result: z.number().nullable()
|
||||
});
|
||||
|
||||
export const schemaOperationData = schemaOperation.pick({
|
||||
alias: true,
|
||||
title: true,
|
||||
description: true,
|
||||
parent: true
|
||||
const schemaOperationData = schemaOperation
|
||||
.pick({
|
||||
parent: true
|
||||
})
|
||||
.extend({
|
||||
alias: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField),
|
||||
title: z.string().max(limits.len_title, errorMsg.titleLength),
|
||||
description: z.string().max(limits.len_description, errorMsg.descriptionLength)
|
||||
});
|
||||
|
||||
const schemaBlockData = schemaOperationData.omit({ alias: true }).extend({
|
||||
title: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField)
|
||||
});
|
||||
|
||||
export const schemaBlock = z.strictObject({
|
||||
|
@ -144,11 +152,7 @@ export const schemaOperationSchema = schemaLibraryItem.extend({
|
|||
|
||||
export const schemaCreateBlock = z.strictObject({
|
||||
layout: schemaOssLayout,
|
||||
item_data: z.strictObject({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
parent: z.number().nullable()
|
||||
}),
|
||||
item_data: schemaBlockData,
|
||||
position: schemaPosition,
|
||||
children_operations: z.array(z.number()),
|
||||
children_blocks: z.array(z.number())
|
||||
|
@ -162,11 +166,7 @@ export const schemaBlockCreatedResponse = z.strictObject({
|
|||
export const schemaUpdateBlock = z.strictObject({
|
||||
target: z.number(),
|
||||
layout: schemaOssLayout,
|
||||
item_data: z.strictObject({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
parent: z.number().nullable()
|
||||
})
|
||||
item_data: schemaBlockData
|
||||
});
|
||||
|
||||
export const schemaDeleteBlock = z.strictObject({
|
||||
|
@ -191,12 +191,7 @@ export const schemaCreateSynthesis = z.strictObject({
|
|||
export const schemaImportSchema = z.strictObject({
|
||||
layout: schemaOssLayout,
|
||||
item_data: schemaOperationData,
|
||||
position: z.strictObject({
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
width: z.number(),
|
||||
height: z.number()
|
||||
}),
|
||||
position: schemaPosition,
|
||||
source: z.number(),
|
||||
clone_source: z.boolean()
|
||||
});
|
||||
|
@ -209,12 +204,7 @@ export const schemaOperationCreatedResponse = z.strictObject({
|
|||
export const schemaUpdateOperation = z.strictObject({
|
||||
target: z.number(),
|
||||
layout: schemaOssLayout,
|
||||
item_data: z.strictObject({
|
||||
alias: z.string().nonempty(errorMsg.requiredField),
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
parent: z.number().nullable()
|
||||
}),
|
||||
item_data: schemaOperationData,
|
||||
arguments: z.array(z.number()),
|
||||
substitutions: z.array(schemaSubstituteConstituents)
|
||||
});
|
||||
|
|
|
@ -44,7 +44,7 @@ export function EditorOssCard() {
|
|||
<div
|
||||
onKeyDown={handleInput}
|
||||
className={clsx(
|
||||
'relative md:w-fit md:max-w-fit max-w-128',
|
||||
'relative md:w-fit md:max-w-fit max-w-136',
|
||||
'flex px-6 pt-8',
|
||||
isNarrow && 'flex-col md:items-center'
|
||||
)}
|
||||
|
@ -65,7 +65,7 @@ export function EditorOssCard() {
|
|||
|
||||
<OssStats
|
||||
className={clsx(
|
||||
'w-80 md:w-56 mt-3 md:mt-8 mx-auto md:ml-5 md:mr-0',
|
||||
'w-80 md:w-56 mt-3 md:mt-8 mx-auto md:ml-8 md:mr-0',
|
||||
'cc-animate-sidebar',
|
||||
showOSSStats ? 'max-w-full' : 'opacity-0 max-w-0'
|
||||
)}
|
||||
|
|
|
@ -41,7 +41,8 @@ export function FormOSS() {
|
|||
description: schema.description,
|
||||
visible: schema.visible,
|
||||
read_only: schema.read_only
|
||||
}
|
||||
},
|
||||
mode: 'onChange'
|
||||
});
|
||||
const visible = useWatch({ control, name: 'visible' });
|
||||
const readOnly = useWatch({ control, name: 'read_only' });
|
||||
|
|
|
@ -2,6 +2,7 @@ import { z } from 'zod';
|
|||
|
||||
import { schemaLibraryItem, schemaVersionInfo } from '@/features/library/backend/types';
|
||||
|
||||
import { limits } from '@/utils/constants';
|
||||
import { errorMsg } from '@/utils/labels';
|
||||
|
||||
/** Represents {@link IConstituenta} type. */
|
||||
|
@ -320,14 +321,14 @@ export const schemaVersionCreatedResponse = z.strictObject({
|
|||
export const schemaCreateConstituenta = schemaConstituentaBasics
|
||||
.pick({
|
||||
cst_type: true,
|
||||
alias: true,
|
||||
convention: true,
|
||||
definition_formal: true,
|
||||
definition_raw: true,
|
||||
term_raw: true,
|
||||
term_forms: true
|
||||
})
|
||||
.extend({
|
||||
alias: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField),
|
||||
convention: z.string().max(limits.len_description, errorMsg.descriptionLength),
|
||||
definition_formal: z.string().max(limits.len_description, errorMsg.descriptionLength),
|
||||
definition_raw: z.string().max(limits.len_description, errorMsg.descriptionLength),
|
||||
term_raw: z.string().max(limits.len_description, errorMsg.descriptionLength),
|
||||
insert_after: z.number().nullable()
|
||||
});
|
||||
|
||||
|
@ -339,13 +340,20 @@ export const schemaConstituentaCreatedResponse = z.strictObject({
|
|||
export const schemaUpdateConstituenta = z.strictObject({
|
||||
target: z.number(),
|
||||
item_data: z.strictObject({
|
||||
alias: z.string().optional(),
|
||||
alias: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField).optional(),
|
||||
cst_type: schemaCstType.optional(),
|
||||
convention: z.string().optional(),
|
||||
definition_formal: z.string().optional(),
|
||||
definition_raw: z.string().optional(),
|
||||
term_raw: z.string().optional(),
|
||||
term_forms: z.array(z.strictObject({ text: z.string(), tags: z.string() })).optional()
|
||||
convention: z.string().max(limits.len_description, errorMsg.descriptionLength).optional(),
|
||||
definition_formal: z.string().max(limits.len_description, errorMsg.descriptionLength).optional(),
|
||||
definition_raw: z.string().max(limits.len_description, errorMsg.descriptionLength).optional(),
|
||||
term_raw: z.string().max(limits.len_description, errorMsg.descriptionLength).optional(),
|
||||
term_forms: z
|
||||
.array(
|
||||
z.strictObject({
|
||||
text: z.string().max(limits.len_description, errorMsg.descriptionLength),
|
||||
tags: z.string().max(limits.len_alias, errorMsg.aliasLength)
|
||||
})
|
||||
)
|
||||
.optional()
|
||||
})
|
||||
});
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ export function EditorRSFormCard() {
|
|||
<div
|
||||
onKeyDown={handleInput}
|
||||
className={clsx(
|
||||
'relative md:w-fit md:max-w-fit max-w-128',
|
||||
'relative md:w-fit md:max-w-fit max-w-136',
|
||||
'flex px-6 pt-8',
|
||||
isNarrow && 'flex-col md:items-center'
|
||||
)}
|
||||
|
|
|
@ -51,7 +51,8 @@ export function FormRSForm() {
|
|||
description: schema.description,
|
||||
visible: schema.visible,
|
||||
read_only: schema.read_only
|
||||
}
|
||||
},
|
||||
mode: 'onChange'
|
||||
});
|
||||
const visible = useWatch({ control, name: 'visible' });
|
||||
const readOnly = useWatch({ control, name: 'read_only' });
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import { patterns } from '@/utils/constants';
|
||||
import { limits, patterns } from '@/utils/constants';
|
||||
import { errorMsg } from '@/utils/labels';
|
||||
|
||||
/** Represents user profile for viewing and editing. */
|
||||
|
@ -29,20 +29,24 @@ export const schemaUserProfile = schemaUser.omit({ is_staff: true });
|
|||
|
||||
export const schemaUserInfo = schemaUser.omit({ username: true, email: true, is_staff: true });
|
||||
|
||||
export const schemaUserSignup = z
|
||||
.object({
|
||||
username: z.string().nonempty(errorMsg.requiredField).regex(RegExp(patterns.login), errorMsg.loginFormat),
|
||||
email: z.string().email(errorMsg.emailField),
|
||||
first_name: z.string(),
|
||||
last_name: z.string(),
|
||||
const schemaUserInput = z.strictObject({
|
||||
username: z
|
||||
.string()
|
||||
.nonempty(errorMsg.requiredField)
|
||||
.regex(RegExp(patterns.login), errorMsg.loginFormat)
|
||||
.max(limits.len_alias, errorMsg.aliasLength),
|
||||
email: z.string().email(errorMsg.emailField).max(limits.len_email, errorMsg.emailLength),
|
||||
first_name: z.string().max(limits.len_alias, errorMsg.aliasLength),
|
||||
last_name: z.string().max(limits.len_alias, errorMsg.aliasLength)
|
||||
});
|
||||
|
||||
password: z.string().nonempty(errorMsg.requiredField),
|
||||
password2: z.string().nonempty(errorMsg.requiredField)
|
||||
export const schemaUserSignup = schemaUserInput
|
||||
.extend({
|
||||
password: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField),
|
||||
password2: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField)
|
||||
})
|
||||
.refine(schema => schema.password === schema.password2, { path: ['password2'], message: errorMsg.passwordsMismatch });
|
||||
|
||||
export const schemaUpdateProfile = z.strictObject({
|
||||
email: z.string().email(errorMsg.emailField),
|
||||
first_name: z.string(),
|
||||
last_name: z.string()
|
||||
export const schemaUpdateProfile = schemaUserInput.omit({
|
||||
username: true
|
||||
});
|
||||
|
|
|
@ -21,7 +21,8 @@ export function EditorPassword() {
|
|||
clearErrors,
|
||||
formState: { errors }
|
||||
} = useForm<IChangePasswordDTO>({
|
||||
resolver: zodResolver(schemaChangePassword)
|
||||
resolver: zodResolver(schemaChangePassword),
|
||||
mode: 'onChange'
|
||||
});
|
||||
|
||||
function resetErrors() {
|
||||
|
|
|
@ -30,7 +30,8 @@ export function EditorProfile() {
|
|||
first_name: profile.first_name,
|
||||
last_name: profile.last_name,
|
||||
email: profile.email
|
||||
}
|
||||
},
|
||||
mode: 'onChange'
|
||||
});
|
||||
|
||||
useBlockNavigation(isDirty);
|
||||
|
|
|
@ -4,22 +4,22 @@
|
|||
|
||||
/** Global application Parameters. The place where magic numbers are put to rest. */
|
||||
export const PARAMETER = {
|
||||
smallScreen: 640, // == tailwind:sm
|
||||
smallScreen: 640, // Tailwind CSS 'sm' breakpoint for small screens (in pixels)
|
||||
|
||||
minimalTimeout: 10, // milliseconds delay for fast updates
|
||||
refreshTimeout: 100, // milliseconds delay for post-refresh actions
|
||||
notificationDelay: 300, // milliseconds delay for notifications
|
||||
zoomDuration: 500, // milliseconds animation duration
|
||||
navigationPopupDelay: 300, // milliseconds delay for navigation popup
|
||||
minimalTimeout: 10, // Minimum delay for rapid UI updates (in milliseconds)
|
||||
refreshTimeout: 100, // Delay after refresh actions to allow UI to settle (in milliseconds)
|
||||
notificationDelay: 300, // Duration to display notifications (in milliseconds)
|
||||
zoomDuration: 500, // Duration of zoom animations (in milliseconds)
|
||||
navigationPopupDelay: 300, // Delay before showing navigation popups (in milliseconds)
|
||||
|
||||
moveDuration: 500, // milliseconds - duration of move animation
|
||||
moveDuration: 500, // Duration of move animations (in milliseconds)
|
||||
|
||||
ossImageWidth: 1280, // pixels - size of OSS image
|
||||
ossImageHeight: 960, // pixels - size of OSS image
|
||||
ossImageWidth: 1280, // Default width for OSS images (in pixels)
|
||||
ossImageHeight: 960, // Default height for OSS images (in pixels)
|
||||
|
||||
graphHandleSize: 3, // pixels - size of graph connection handle
|
||||
graphNodePadding: 5, // pixels - padding of graph node
|
||||
graphNodeRadius: 20, // pixels - radius of graph node
|
||||
graphHandleSize: 3, // Size of graph connection handles (in pixels)
|
||||
graphNodePadding: 5, // Padding inside graph nodes (in pixels)
|
||||
graphNodeRadius: 20, // Radius of graph nodes (in pixels)
|
||||
|
||||
logicLabel: 'LOGIC',
|
||||
errorNodeLabel: '[ERROR]',
|
||||
|
@ -28,7 +28,12 @@ export const PARAMETER = {
|
|||
|
||||
/** Numeric limitations. */
|
||||
export const limits = {
|
||||
location_len: 500
|
||||
len_alias: 255,
|
||||
len_email: 320,
|
||||
len_title: 500,
|
||||
len_location: 500,
|
||||
len_description: 10000,
|
||||
len_text: 20000
|
||||
} as const;
|
||||
|
||||
/** Exteor file extension for RSForm. */
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* Description is a long description used in tooltips.
|
||||
*/
|
||||
|
||||
import { limits } from './constants';
|
||||
|
||||
/**
|
||||
* UI info descriptors.
|
||||
*/
|
||||
|
@ -48,6 +50,11 @@ export const infoMsg = {
|
|||
*/
|
||||
export const errorMsg = {
|
||||
astFailed: 'Невозможно построить дерево разбора',
|
||||
aliasLength: `до ${limits.len_alias} символов`,
|
||||
emailLength: `до ${limits.len_email} символов`,
|
||||
titleLength: `до ${limits.len_title} символов`,
|
||||
descriptionLength: `до ${limits.len_description} символов`,
|
||||
textLength: `до ${limits.len_text} символов`,
|
||||
typeStructureFailed: 'Структура отсутствует',
|
||||
passwordsMismatch: 'Пароли не совпадают',
|
||||
passwordsSame: 'Пароль совпадает со старым',
|
||||
|
|
Loading…
Reference in New Issue
Block a user