F: Implement frontend api calls

This commit is contained in:
Ivan 2025-04-20 18:06:18 +03:00
parent c07bdfbb3a
commit 2ae9576384
22 changed files with 419 additions and 251 deletions

View File

@ -7,18 +7,18 @@ from .data_access import (
CreateBlockSerializer, CreateBlockSerializer,
CreateOperationSerializer, CreateOperationSerializer,
DeleteBlockSerializer, DeleteBlockSerializer,
OperationDeleteSerializer, DeleteOperationSerializer,
OperationSchemaSerializer, OperationSchemaSerializer,
OperationSerializer, OperationSerializer,
OperationTargetSerializer,
RelocateConstituentsSerializer, RelocateConstituentsSerializer,
SetOperationInputSerializer, SetOperationInputSerializer,
TargetOperationSerializer,
UpdateBlockSerializer, UpdateBlockSerializer,
UpdateOperationSerializer UpdateOperationSerializer
) )
from .responses import ( from .responses import (
BlockCreatedResponse,
ConstituentaReferenceResponse, ConstituentaReferenceResponse,
NewBlockResponse, OperationCreatedResponse,
NewOperationResponse, SchemaCreatedResponse
NewSchemaResponse
) )

View File

@ -83,6 +83,55 @@ class CreateBlockSerializer(serializers.Serializer):
return attrs return attrs
class UpdateBlockSerializer(serializers.Serializer):
''' Serializer: Block update. '''
class UpdateBlockData(serializers.ModelSerializer):
''' Serializer: Block update data. '''
class Meta:
''' serializer metadata. '''
model = Block
fields = 'title', 'description', 'parent'
layout = LayoutSerializer(required=False)
target = PKField(many=False, queryset=Block.objects.all())
item_data = UpdateBlockData()
def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss'])
block = cast(Block, attrs['target'])
if block.oss_id != oss.pk:
raise serializers.ValidationError({
'target': msg.blockNotInOSS()
})
if 'parent' in attrs['item_data'] and \
attrs['item_data']['parent'] is not None:
if attrs['item_data']['parent'].oss_id != oss.pk:
raise serializers.ValidationError({
'parent': msg.parentNotInOSS()
})
if attrs['item_data']['parent'] == attrs['target']:
raise serializers.ValidationError({
'parent': msg.blockSelfParent()
})
return attrs
class DeleteBlockSerializer(serializers.Serializer):
''' Serializer: Delete block. '''
layout = LayoutSerializer()
target = PKField(many=False, queryset=Block.objects.all().only('oss_id'))
def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss'])
block = cast(Block, attrs['target'])
if block.oss_id != oss.pk:
raise serializers.ValidationError({
'target': msg.blockNotInOSS()
})
return attrs
class CreateOperationSerializer(serializers.Serializer): class CreateOperationSerializer(serializers.Serializer):
''' Serializer: Operation creation. ''' ''' Serializer: Operation creation. '''
class CreateOperationData(serializers.ModelSerializer): class CreateOperationData(serializers.ModelSerializer):
@ -196,56 +245,7 @@ class UpdateOperationSerializer(serializers.Serializer):
return attrs return attrs
class UpdateBlockSerializer(serializers.Serializer): class DeleteOperationSerializer(serializers.Serializer):
''' Serializer: Block update. '''
class UpdateBlockData(serializers.ModelSerializer):
''' Serializer: Block update data. '''
class Meta:
''' serializer metadata. '''
model = Block
fields = 'title', 'description', 'parent'
layout = LayoutSerializer(required=False)
target = PKField(many=False, queryset=Block.objects.all())
item_data = UpdateBlockData()
def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss'])
block = cast(Block, attrs['target'])
if block.oss_id != oss.pk:
raise serializers.ValidationError({
'target': msg.blockNotInOSS()
})
if 'parent' in attrs['item_data'] and \
attrs['item_data']['parent'] is not None:
if attrs['item_data']['parent'].oss_id != oss.pk:
raise serializers.ValidationError({
'parent': msg.parentNotInOSS()
})
if attrs['item_data']['parent'] == attrs['target']:
raise serializers.ValidationError({
'parent': msg.blockSelfParent()
})
return attrs
class DeleteBlockSerializer(serializers.Serializer):
''' Serializer: Delete block. '''
layout = LayoutSerializer()
target = PKField(many=False, queryset=Block.objects.all().only('oss_id'))
def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss'])
block = cast(Block, attrs['target'])
if block.oss_id != oss.pk:
raise serializers.ValidationError({
'target': msg.blockNotInOSS()
})
return attrs
class OperationDeleteSerializer(serializers.Serializer):
''' Serializer: Delete operation. ''' ''' Serializer: Delete operation. '''
layout = LayoutSerializer() layout = LayoutSerializer()
target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result')) target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result'))
@ -262,7 +262,7 @@ class OperationDeleteSerializer(serializers.Serializer):
return attrs return attrs
class OperationTargetSerializer(serializers.Serializer): class TargetOperationSerializer(serializers.Serializer):
''' Serializer: Target single operation. ''' ''' Serializer: Target single operation. '''
layout = LayoutSerializer() layout = LayoutSerializer()
target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result_id')) target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result_id'))

View File

@ -6,19 +6,19 @@ from apps.library.serializers import LibraryItemSerializer
from .data_access import BlockSerializer, OperationSchemaSerializer, OperationSerializer from .data_access import BlockSerializer, OperationSchemaSerializer, OperationSerializer
class NewOperationResponse(serializers.Serializer): class OperationCreatedResponse(serializers.Serializer):
''' Serializer: Create operation response. ''' ''' Serializer: Create operation response. '''
new_operation = OperationSerializer() new_operation = OperationSerializer()
oss = OperationSchemaSerializer() oss = OperationSchemaSerializer()
class NewBlockResponse(serializers.Serializer): class BlockCreatedResponse(serializers.Serializer):
''' Serializer: Create block response. ''' ''' Serializer: Create block response. '''
new_block = BlockSerializer() new_block = BlockSerializer()
oss = OperationSchemaSerializer() oss = OperationSchemaSerializer()
class NewSchemaResponse(serializers.Serializer): class SchemaCreatedResponse(serializers.Serializer):
''' Serializer: Create RSForm for input operation response. ''' ''' Serializer: Create RSForm for input operation response. '''
new_schema = LibraryItemSerializer() new_schema = LibraryItemSerializer()
oss = OperationSchemaSerializer() oss = OperationSchemaSerializer()

View File

@ -37,12 +37,12 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
''' Determine permission class. ''' ''' Determine permission class. '''
if self.action in [ if self.action in [
'update_layout', 'update_layout',
'create_operation',
'create_block', 'create_block',
'delete_operation',
'delete_block',
'update_operation',
'update_block', 'update_block',
'delete_block',
'create_operation',
'update_operation',
'delete_operation',
'create_input', 'create_input',
'set_input', 'set_input',
'execute_operation', 'execute_operation',
@ -93,12 +93,130 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
m.OperationSchema(self.get_object()).update_layout(serializer.validated_data) m.OperationSchema(self.get_object()).update_layout(serializer.validated_data)
return Response(status=c.HTTP_200_OK) return Response(status=c.HTTP_200_OK)
@extend_schema(
summary='create block',
tags=['OSS'],
request=s.CreateBlockSerializer(),
responses={
c.HTTP_201_CREATED: s.BlockCreatedResponse,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=True, methods=['post'], url_path='create-block')
def create_block(self, request: Request, pk) -> HttpResponse:
''' Create Block. '''
serializer = s.CreateBlockSerializer(
data=request.data,
context={'oss': self.get_object()}
)
serializer.is_valid(raise_exception=True)
oss = m.OperationSchema(self.get_object())
layout = serializer.validated_data['layout']
children_blocks: list[m.Block] = serializer.validated_data['children_blocks']
children_operations: list[m.Operation] = serializer.validated_data['children_operations']
with transaction.atomic():
new_block = oss.create_block(**serializer.validated_data['item_data'])
layout['blocks'].append({
'id': new_block.pk,
'x': serializer.validated_data['position_x'],
'y': serializer.validated_data['position_y'],
'width': serializer.validated_data['width'],
'height': serializer.validated_data['height'],
})
oss.update_layout(layout)
if len(children_blocks) > 0:
for block in children_blocks:
block.parent = new_block
m.Block.objects.bulk_update(children_blocks, ['parent'])
if len(children_operations) > 0:
for operation in children_operations:
operation.parent = new_block
m.Operation.objects.bulk_update(children_operations, ['parent'])
return Response(
status=c.HTTP_201_CREATED,
data={
'new_block': s.BlockSerializer(new_block).data,
'oss': s.OperationSchemaSerializer(oss.model).data
}
)
@extend_schema(
summary='update block',
tags=['OSS'],
request=s.UpdateBlockSerializer(),
responses={
c.HTTP_200_OK: s.OperationSchemaSerializer,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=True, methods=['patch'], url_path='update-block')
def update_block(self, request: Request, pk) -> HttpResponse:
''' Update Block. '''
serializer = s.UpdateBlockSerializer(
data=request.data,
context={'oss': self.get_object()}
)
serializer.is_valid(raise_exception=True)
block: m.Block = cast(m.Block, serializer.validated_data['target'])
oss = m.OperationSchema(self.get_object())
with transaction.atomic():
if 'layout' in serializer.validated_data:
oss.update_layout(serializer.validated_data['layout'])
block.title = serializer.validated_data['item_data']['title']
block.description = serializer.validated_data['item_data']['description']
block.parent = serializer.validated_data['item_data']['parent']
block.save(update_fields=['title', 'description', 'parent'])
return Response(
status=c.HTTP_200_OK,
data=s.OperationSchemaSerializer(oss.model).data
)
@extend_schema(
summary='delete block',
tags=['OSS'],
request=s.DeleteBlockSerializer,
responses={
c.HTTP_200_OK: s.OperationSchemaSerializer,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=True, methods=['patch'], url_path='delete-block')
def delete_block(self, request: Request, pk) -> HttpResponse:
''' Endpoint: Delete Block. '''
serializer = s.DeleteBlockSerializer(
data=request.data,
context={'oss': self.get_object()}
)
serializer.is_valid(raise_exception=True)
oss = m.OperationSchema(self.get_object())
block = cast(m.Block, serializer.validated_data['target'])
layout = serializer.validated_data['layout']
layout['blocks'] = [x for x in layout['blocks'] if x['id'] != block.pk]
with transaction.atomic():
oss.delete_block(block)
oss.update_layout(layout)
return Response(
status=c.HTTP_200_OK,
data=s.OperationSchemaSerializer(oss.model).data
)
@extend_schema( @extend_schema(
summary='create operation', summary='create operation',
tags=['OSS'], tags=['OSS'],
request=s.CreateOperationSerializer(), request=s.CreateOperationSerializer(),
responses={ responses={
c.HTTP_201_CREATED: s.NewOperationResponse, c.HTTP_201_CREATED: s.OperationCreatedResponse,
c.HTTP_400_BAD_REQUEST: None, c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None, c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None c.HTTP_404_NOT_FOUND: None
@ -154,130 +272,6 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
} }
) )
@extend_schema(
summary='create block',
tags=['OSS'],
request=s.CreateBlockSerializer(),
responses={
c.HTTP_201_CREATED: s.NewBlockResponse,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=True, methods=['post'], url_path='create-block')
def create_block(self, request: Request, pk) -> HttpResponse:
''' Create Block. '''
serializer = s.CreateBlockSerializer(
data=request.data,
context={'oss': self.get_object()}
)
serializer.is_valid(raise_exception=True)
oss = m.OperationSchema(self.get_object())
layout = serializer.validated_data['layout']
children_blocks: list[m.Block] = serializer.validated_data['children_blocks']
children_operations: list[m.Operation] = serializer.validated_data['children_operations']
with transaction.atomic():
new_block = oss.create_block(**serializer.validated_data['item_data'])
layout['blocks'].append({
'id': new_block.pk,
'x': serializer.validated_data['position_x'],
'y': serializer.validated_data['position_y'],
'width': serializer.validated_data['width'],
'height': serializer.validated_data['height'],
})
oss.update_layout(layout)
if len(children_blocks) > 0:
for block in children_blocks:
block.parent = new_block
m.Block.objects.bulk_update(children_blocks, ['parent'])
if len(children_operations) > 0:
for operation in children_operations:
operation.parent = new_block
m.Operation.objects.bulk_update(children_operations, ['parent'])
return Response(
status=c.HTTP_201_CREATED,
data={
'new_block': s.BlockSerializer(new_block).data,
'oss': s.OperationSchemaSerializer(oss.model).data
}
)
@extend_schema(
summary='delete operation',
tags=['OSS'],
request=s.OperationDeleteSerializer,
responses={
c.HTTP_200_OK: s.OperationSchemaSerializer,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=True, methods=['patch'], url_path='delete-operation')
def delete_operation(self, request: Request, pk) -> HttpResponse:
''' Endpoint: Delete Operation. '''
serializer = s.OperationDeleteSerializer(
data=request.data,
context={'oss': self.get_object()}
)
serializer.is_valid(raise_exception=True)
oss = m.OperationSchema(self.get_object())
operation = cast(m.Operation, serializer.validated_data['target'])
old_schema = operation.result
layout = serializer.validated_data['layout']
layout['operations'] = [x for x in layout['operations'] if x['id'] != operation.pk]
with transaction.atomic():
oss.delete_operation(operation.pk, serializer.validated_data['keep_constituents'])
oss.update_layout(layout)
if old_schema is not None:
if serializer.validated_data['delete_schema']:
m.PropagationFacade.before_delete_schema(old_schema)
old_schema.delete()
elif old_schema.is_synced(oss.model):
old_schema.visible = True
old_schema.save(update_fields=['visible'])
return Response(
status=c.HTTP_200_OK,
data=s.OperationSchemaSerializer(oss.model).data
)
@extend_schema(
summary='delete block',
tags=['OSS'],
request=s.DeleteBlockSerializer,
responses={
c.HTTP_200_OK: s.OperationSchemaSerializer,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=True, methods=['patch'], url_path='delete-block')
def delete_block(self, request: Request, pk) -> HttpResponse:
''' Endpoint: Delete Block. '''
serializer = s.DeleteBlockSerializer(
data=request.data,
context={'oss': self.get_object()}
)
serializer.is_valid(raise_exception=True)
oss = m.OperationSchema(self.get_object())
block = cast(m.Block, serializer.validated_data['target'])
layout = serializer.validated_data['layout']
layout['blocks'] = [x for x in layout['blocks'] if x['id'] != block.pk]
with transaction.atomic():
oss.delete_block(block)
oss.update_layout(layout)
return Response(
status=c.HTTP_200_OK,
data=s.OperationSchemaSerializer(oss.model).data
)
@extend_schema( @extend_schema(
summary='update operation', summary='update operation',
tags=['OSS'], tags=['OSS'],
@ -325,9 +319,9 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
) )
@extend_schema( @extend_schema(
summary='update block', summary='delete operation',
tags=['OSS'], tags=['OSS'],
request=s.UpdateBlockSerializer(), request=s.DeleteOperationSerializer,
responses={ responses={
c.HTTP_200_OK: s.OperationSchemaSerializer, c.HTTP_200_OK: s.OperationSchemaSerializer,
c.HTTP_400_BAD_REQUEST: None, c.HTTP_400_BAD_REQUEST: None,
@ -335,24 +329,30 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
c.HTTP_404_NOT_FOUND: None c.HTTP_404_NOT_FOUND: None
} }
) )
@action(detail=True, methods=['patch'], url_path='update-block') @action(detail=True, methods=['patch'], url_path='delete-operation')
def update_block(self, request: Request, pk) -> HttpResponse: def delete_operation(self, request: Request, pk) -> HttpResponse:
''' Update Block. ''' ''' Endpoint: Delete Operation. '''
serializer = s.UpdateBlockSerializer( serializer = s.DeleteOperationSerializer(
data=request.data, data=request.data,
context={'oss': self.get_object()} context={'oss': self.get_object()}
) )
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
block: m.Block = cast(m.Block, serializer.validated_data['target'])
oss = m.OperationSchema(self.get_object()) oss = m.OperationSchema(self.get_object())
operation = cast(m.Operation, serializer.validated_data['target'])
old_schema = operation.result
layout = serializer.validated_data['layout']
layout['operations'] = [x for x in layout['operations'] if x['id'] != operation.pk]
with transaction.atomic(): with transaction.atomic():
if 'layout' in serializer.validated_data: oss.delete_operation(operation.pk, serializer.validated_data['keep_constituents'])
oss.update_layout(serializer.validated_data['layout']) oss.update_layout(layout)
block.title = serializer.validated_data['item_data']['title'] if old_schema is not None:
block.description = serializer.validated_data['item_data']['description'] if serializer.validated_data['delete_schema']:
block.parent = serializer.validated_data['item_data']['parent'] m.PropagationFacade.before_delete_schema(old_schema)
block.save(update_fields=['title', 'description', 'parent']) old_schema.delete()
elif old_schema.is_synced(oss.model):
old_schema.visible = True
old_schema.save(update_fields=['visible'])
return Response( return Response(
status=c.HTTP_200_OK, status=c.HTTP_200_OK,
data=s.OperationSchemaSerializer(oss.model).data data=s.OperationSchemaSerializer(oss.model).data
@ -361,9 +361,9 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
@extend_schema( @extend_schema(
summary='create input schema for target operation', summary='create input schema for target operation',
tags=['OSS'], tags=['OSS'],
request=s.OperationTargetSerializer(), request=s.TargetOperationSerializer(),
responses={ responses={
c.HTTP_200_OK: s.NewSchemaResponse, c.HTTP_200_OK: s.SchemaCreatedResponse,
c.HTTP_400_BAD_REQUEST: None, c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None, c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None c.HTTP_404_NOT_FOUND: None
@ -372,7 +372,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
@action(detail=True, methods=['patch'], url_path='create-input') @action(detail=True, methods=['patch'], url_path='create-input')
def create_input(self, request: Request, pk) -> HttpResponse: def create_input(self, request: Request, pk) -> HttpResponse:
''' Create input RSForm. ''' ''' Create input RSForm. '''
serializer = s.OperationTargetSerializer( serializer = s.TargetOperationSerializer(
data=request.data, data=request.data,
context={'oss': self.get_object()} context={'oss': self.get_object()}
) )
@ -451,7 +451,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
@extend_schema( @extend_schema(
summary='execute operation', summary='execute operation',
tags=['OSS'], tags=['OSS'],
request=s.OperationTargetSerializer(), request=s.TargetOperationSerializer(),
responses={ responses={
c.HTTP_200_OK: s.OperationSchemaSerializer, c.HTTP_200_OK: s.OperationSchemaSerializer,
c.HTTP_400_BAD_REQUEST: None, c.HTTP_400_BAD_REQUEST: None,
@ -462,7 +462,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
@action(detail=True, methods=['post'], url_path='execute-operation') @action(detail=True, methods=['post'], url_path='execute-operation')
def execute_operation(self, request: Request, pk) -> HttpResponse: def execute_operation(self, request: Request, pk) -> HttpResponse:
''' Execute operation. ''' ''' Execute operation. '''
serializer = s.OperationTargetSerializer( serializer = s.TargetOperationSerializer(
data=request.data, data=request.data,
context={'oss': self.get_object()} context={'oss': self.get_object()}
) )

View File

@ -5,8 +5,11 @@ import { DELAYS, KEYS } from '@/backend/configuration';
import { infoMsg } from '@/utils/labels'; import { infoMsg } from '@/utils/labels';
import { import {
type IBlockCreatedResponse,
type IConstituentaReference, type IConstituentaReference,
type ICreateBlockDTO,
type ICreateOperationDTO, type ICreateOperationDTO,
type IDeleteBlockDTO,
type IDeleteOperationDTO, type IDeleteOperationDTO,
type IInputCreatedResponse, type IInputCreatedResponse,
type IOperationCreatedResponse, type IOperationCreatedResponse,
@ -14,6 +17,7 @@ import {
type IOssLayout, type IOssLayout,
type IRelocateConstituentsDTO, type IRelocateConstituentsDTO,
type ITargetOperation, type ITargetOperation,
type IUpdateBlockDTO,
type IUpdateInputDTO, type IUpdateInputDTO,
type IUpdateOperationDTO, type IUpdateOperationDTO,
schemaConstituentaReference, schemaConstituentaReference,
@ -49,6 +53,34 @@ export const ossApi = {
} }
}), }),
createBlock: ({ itemID, data }: { itemID: number; data: ICreateBlockDTO }) =>
axiosPost<ICreateBlockDTO, IBlockCreatedResponse>({
schema: schemaOperationCreatedResponse,
endpoint: `/api/oss/${itemID}/create-block`,
request: {
data: data,
successMessage: infoMsg.changesSaved
}
}),
updateBlock: ({ itemID, data }: { itemID: number; data: IUpdateBlockDTO }) =>
axiosPatch<IUpdateBlockDTO, IOperationSchemaDTO>({
schema: schemaOperationSchema,
endpoint: `/api/oss/${itemID}/update-operation`,
request: {
data: data,
successMessage: infoMsg.changesSaved
}
}),
deleteBlock: ({ itemID, data }: { itemID: number; data: IDeleteBlockDTO }) =>
axiosPatch<IDeleteBlockDTO, IOperationSchemaDTO>({
schema: schemaOperationSchema,
endpoint: `/api/oss/${itemID}/delete-operation`,
request: {
data: data,
successMessage: infoMsg.operationDestroyed
}
}),
createOperation: ({ itemID, data }: { itemID: number; data: ICreateOperationDTO }) => createOperation: ({ itemID, data }: { itemID: number; data: ICreateOperationDTO }) =>
axiosPost<ICreateOperationDTO, IOperationCreatedResponse>({ axiosPost<ICreateOperationDTO, IOperationCreatedResponse>({
schema: schemaOperationCreatedResponse, schema: schemaOperationCreatedResponse,
@ -58,6 +90,15 @@ export const ossApi = {
successMessage: response => infoMsg.newOperation(response.new_operation.alias) successMessage: response => infoMsg.newOperation(response.new_operation.alias)
} }
}), }),
updateOperation: ({ itemID, data }: { itemID: number; data: IUpdateOperationDTO }) =>
axiosPatch<IUpdateOperationDTO, IOperationSchemaDTO>({
schema: schemaOperationSchema,
endpoint: `/api/oss/${itemID}/update-operation`,
request: {
data: data,
successMessage: infoMsg.changesSaved
}
}),
deleteOperation: ({ itemID, data }: { itemID: number; data: IDeleteOperationDTO }) => deleteOperation: ({ itemID, data }: { itemID: number; data: IDeleteOperationDTO }) =>
axiosPatch<IDeleteOperationDTO, IOperationSchemaDTO>({ axiosPatch<IDeleteOperationDTO, IOperationSchemaDTO>({
schema: schemaOperationSchema, schema: schemaOperationSchema,
@ -67,6 +108,7 @@ export const ossApi = {
successMessage: infoMsg.operationDestroyed successMessage: infoMsg.operationDestroyed
} }
}), }),
createInput: ({ itemID, data }: { itemID: number; data: ITargetOperation }) => createInput: ({ itemID, data }: { itemID: number; data: ITargetOperation }) =>
axiosPatch<ITargetOperation, IInputCreatedResponse>({ axiosPatch<ITargetOperation, IInputCreatedResponse>({
schema: schemaInputCreatedResponse, schema: schemaInputCreatedResponse,
@ -85,15 +127,6 @@ export const ossApi = {
successMessage: infoMsg.changesSaved successMessage: infoMsg.changesSaved
} }
}), }),
updateOperation: ({ itemID, data }: { itemID: number; data: IUpdateOperationDTO }) =>
axiosPatch<IUpdateOperationDTO, IOperationSchemaDTO>({
schema: schemaOperationSchema,
endpoint: `/api/oss/${itemID}/update-operation`,
request: {
data: data,
successMessage: infoMsg.changesSaved
}
}),
executeOperation: ({ itemID, data }: { itemID: number; data: ITargetOperation }) => executeOperation: ({ itemID, data }: { itemID: number; data: ITargetOperation }) =>
axiosPost<ITargetOperation, IOperationSchemaDTO>({ axiosPost<ITargetOperation, IOperationSchemaDTO>({
schema: schemaOperationSchema, schema: schemaOperationSchema,

View File

@ -27,31 +27,42 @@ export type IOperationSchemaDTO = z.infer<typeof schemaOperationSchema>;
/** Represents {@link IOperationSchema} layout. */ /** Represents {@link IOperationSchema} layout. */
export type IOssLayout = z.infer<typeof schemaOssLayout>; export type IOssLayout = z.infer<typeof schemaOssLayout>;
/** Represents {@link IOperation} data, used in creation process. */ /** Represents {@link IBlock} data, used in Create action. */
export type ICreateBlockDTO = z.infer<typeof schemaCreateBlock>;
/** Represents data response when creating {@link IBlock}. */
export type IBlockCreatedResponse = z.infer<typeof schemaBlockCreatedResponse>;
/** Represents {@link IBlock} data, used in Update action. */
export type IUpdateBlockDTO = z.infer<typeof schemaUpdateBlock>;
/** Represents {@link IBlock} data, used in Delete action. */
export type IDeleteBlockDTO = z.infer<typeof schemaDeleteBlock>;
/** Represents {@link IOperation} data, used in Create action. */
export type ICreateOperationDTO = z.infer<typeof schemaCreateOperation>; export type ICreateOperationDTO = z.infer<typeof schemaCreateOperation>;
/** Represents data response when creating {@link IOperation}. */ /** Represents data response when creating {@link IOperation}. */
export type IOperationCreatedResponse = z.infer<typeof schemaOperationCreatedResponse>; export type IOperationCreatedResponse = z.infer<typeof schemaOperationCreatedResponse>;
/**
* Represents target {@link IOperation}. /** Represents {@link IOperation} data, used in Update action. */
*/ export type IUpdateOperationDTO = z.infer<typeof schemaUpdateOperation>;
/** Represents {@link IOperation} data, used in Delete action. */
export type IDeleteOperationDTO = z.infer<typeof schemaDeleteOperation>;
/** Represents target {@link IOperation}. */
export interface ITargetOperation { export interface ITargetOperation {
layout: IOssLayout; layout: IOssLayout;
target: number; target: number;
} }
/** Represents {@link IOperation} data, used in destruction process. */
export type IDeleteOperationDTO = z.infer<typeof schemaDeleteOperation>;
/** Represents data response when creating {@link IRSForm} for Input {@link IOperation}. */ /** Represents data response when creating {@link IRSForm} for Input {@link IOperation}. */
export type IInputCreatedResponse = z.infer<typeof schemaInputCreatedResponse>; export type IInputCreatedResponse = z.infer<typeof schemaInputCreatedResponse>;
/** Represents {@link IOperation} data, used in setInput process. */ /** Represents {@link IOperation} data, used in setInput action. */
export type IUpdateInputDTO = z.infer<typeof schemaUpdateInput>; export type IUpdateInputDTO = z.infer<typeof schemaUpdateInput>;
/** Represents {@link IOperation} data, used in update process. */
export type IUpdateOperationDTO = z.infer<typeof schemaUpdateOperation>;
/** Represents data, used relocating {@link IConstituenta}s between {@link ILibraryItem}s. */ /** Represents data, used relocating {@link IConstituenta}s between {@link ILibraryItem}s. */
export type IRelocateConstituentsDTO = z.infer<typeof schemaRelocateConstituents>; export type IRelocateConstituentsDTO = z.infer<typeof schemaRelocateConstituents>;
@ -121,6 +132,41 @@ export const schemaOperationSchema = schemaLibraryItem.extend({
substitutions: z.array(schemaCstSubstituteInfo) substitutions: z.array(schemaCstSubstituteInfo)
}); });
export const schemaCreateBlock = z.strictObject({
layout: schemaOssLayout,
item_data: z.strictObject({
title: z.string(),
description: z.string(),
parent: z.number().nullable()
}),
position_x: z.number(),
position_y: z.number(),
width: z.number(),
height: z.number(),
children_operations: z.array(z.number()),
children_blocks: z.array(z.number())
});
export const schemaBlockCreatedResponse = z.strictObject({
new_block: schemaBlock,
oss: schemaOperationSchema
});
export const schemaUpdateBlock = z.strictObject({
target: z.number(),
layout: schemaOssLayout,
item_data: z.strictObject({
title: z.string(),
description: z.string(),
parent: z.number().nullable()
})
});
export const schemaDeleteBlock = z.strictObject({
target: z.number(),
layout: schemaOssLayout
});
export const schemaCreateOperation = z.strictObject({ export const schemaCreateOperation = z.strictObject({
layout: schemaOssLayout, layout: schemaOssLayout,
item_data: z.strictObject({ item_data: z.strictObject({
@ -142,6 +188,19 @@ export const schemaOperationCreatedResponse = z.strictObject({
oss: schemaOperationSchema oss: schemaOperationSchema
}); });
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()
}),
arguments: z.array(z.number()),
substitutions: z.array(schemaSubstituteConstituents)
});
export const schemaDeleteOperation = z.strictObject({ export const schemaDeleteOperation = z.strictObject({
target: z.number(), target: z.number(),
layout: schemaOssLayout, layout: schemaOssLayout,
@ -160,18 +219,6 @@ export const schemaInputCreatedResponse = z.strictObject({
oss: schemaOperationSchema oss: schemaOperationSchema
}); });
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()
}),
arguments: z.array(z.number()),
substitutions: z.array(schemaSubstituteConstituents)
});
export const schemaRelocateConstituents = z.strictObject({ export const schemaRelocateConstituents = z.strictObject({
destination: z.number().nullable(), destination: z.number().nullable(),
items: z.array(z.number()).refine(data => data.length > 0) items: z.array(z.number()).refine(data => data.length > 0)

View File

@ -0,0 +1,25 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp';
import { KEYS } from '@/backend/configuration';
import { ossApi } from './api';
import { type ICreateBlockDTO } from './types';
export const useCreateBlock = () => {
const client = useQueryClient();
const { updateTimestamp } = useUpdateTimestamp();
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'create-block'],
mutationFn: ossApi.createBlock,
onSuccess: response => {
client.setQueryData(ossApi.getOssQueryOptions({ itemID: response.oss.id }).queryKey, response.oss);
updateTimestamp(response.oss.id);
},
onError: () => client.invalidateQueries()
});
return {
createBlock: (data: { itemID: number; data: ICreateBlockDTO }) => mutation.mutateAsync(data)
};
};

View File

@ -8,7 +8,7 @@ import { type ITargetOperation } from './types';
export const useCreateInput = () => { export const useCreateInput = () => {
const client = useQueryClient(); const client = useQueryClient();
const mutation = useMutation({ const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'input-create'], mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'create-input'],
mutationFn: ossApi.createInput, mutationFn: ossApi.createInput,
onSuccess: data => { onSuccess: data => {
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.oss.id }).queryKey, data.oss); client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.oss.id }).queryKey, data.oss);

View File

@ -11,7 +11,7 @@ export const useCreateOperation = () => {
const client = useQueryClient(); const client = useQueryClient();
const { updateTimestamp } = useUpdateTimestamp(); const { updateTimestamp } = useUpdateTimestamp();
const mutation = useMutation({ const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'operation-create'], mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'create-operation'],
mutationFn: ossApi.createOperation, mutationFn: ossApi.createOperation,
onSuccess: response => { onSuccess: response => {
client.setQueryData(ossApi.getOssQueryOptions({ itemID: response.oss.id }).queryKey, response.oss); client.setQueryData(ossApi.getOssQueryOptions({ itemID: response.oss.id }).queryKey, response.oss);

View File

@ -0,0 +1,25 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { KEYS } from '@/backend/configuration';
import { ossApi } from './api';
import { type IDeleteBlockDTO } from './types';
export const useDeleteBlock = () => {
const client = useQueryClient();
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'delete-block'],
mutationFn: ossApi.deleteBlock,
onSuccess: data => {
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
return Promise.allSettled([
client.invalidateQueries({ queryKey: KEYS.composite.libraryList }),
client.invalidateQueries({ queryKey: [KEYS.rsform] })
]);
},
onError: () => client.invalidateQueries()
});
return {
deleteBlock: (data: { itemID: number; data: IDeleteBlockDTO }) => mutation.mutateAsync(data)
};
};

View File

@ -8,7 +8,7 @@ import { type IDeleteOperationDTO } from './types';
export const useDeleteOperation = () => { export const useDeleteOperation = () => {
const client = useQueryClient(); const client = useQueryClient();
const mutation = useMutation({ const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'operation-delete'], mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'delete-operation'],
mutationFn: ossApi.deleteOperation, mutationFn: ossApi.deleteOperation,
onSuccess: data => { onSuccess: data => {
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data); client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);

View File

@ -8,7 +8,7 @@ import { type ITargetOperation } from './types';
export const useExecuteOperation = () => { export const useExecuteOperation = () => {
const client = useQueryClient(); const client = useQueryClient();
const mutation = useMutation({ const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'operation-execute'], mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'execute-operation'],
mutationFn: ossApi.executeOperation, mutationFn: ossApi.executeOperation,
onSuccess: data => { onSuccess: data => {
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data); client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);

View File

@ -0,0 +1,37 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { type ILibraryItem } from '@/features/library';
import { KEYS } from '@/backend/configuration';
import { ossApi } from './api';
import { type IUpdateBlockDTO } from './types';
export const useUpdateBlock = () => {
const client = useQueryClient();
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'update-block'],
mutationFn: ossApi.updateBlock,
onSuccess: (data, variables) => {
client.setQueryData(KEYS.composite.ossItem({ itemID: data.id }), data);
const schemaID = data.operations.find(item => item.id === variables.data.target)?.result;
if (!schemaID) {
return;
}
client.setQueryData(KEYS.composite.libraryList, (prev: ILibraryItem[] | undefined) =>
!prev
? undefined
: prev.map(item =>
item.id === schemaID ? { ...item, ...variables.data.item_data, time_update: Date() } : item
)
);
return client.invalidateQueries({
queryKey: KEYS.composite.rsItem({ itemID: schemaID })
});
},
onError: () => client.invalidateQueries()
});
return {
updateBlock: (data: { itemID: number; data: IUpdateBlockDTO }) => mutation.mutateAsync(data)
};
};

View File

@ -8,7 +8,7 @@ import { type IUpdateInputDTO } from './types';
export const useUpdateInput = () => { export const useUpdateInput = () => {
const client = useQueryClient(); const client = useQueryClient();
const mutation = useMutation({ const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'input-update'], mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'update-input'],
mutationFn: ossApi.updateInput, mutationFn: ossApi.updateInput,
onSuccess: data => { onSuccess: data => {
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data); client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);

View File

@ -10,7 +10,7 @@ import { type IUpdateOperationDTO } from './types';
export const useUpdateOperation = () => { export const useUpdateOperation = () => {
const client = useQueryClient(); const client = useQueryClient();
const mutation = useMutation({ const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'operation-update'], mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'update-operation'],
mutationFn: ossApi.updateOperation, mutationFn: ossApi.updateOperation,
onSuccess: (data, variables) => { onSuccess: (data, variables) => {
client.setQueryData(KEYS.composite.ossItem({ itemID: data.id }), data); client.setQueryData(KEYS.composite.ossItem({ itemID: data.id }), data);

View File

@ -43,7 +43,8 @@ export function DlgEditOperation() {
item_data: { item_data: {
alias: target.alias, alias: target.alias,
title: target.title, title: target.title,
description: target.description description: target.description,
parent: target.parent
}, },
arguments: target.arguments, arguments: target.arguments,
substitutions: target.substitutions.map(sub => ({ substitutions: target.substitutions.map(sub => ({

View File

@ -11,7 +11,7 @@ export const useCreateConstituenta = () => {
const client = useQueryClient(); const client = useQueryClient();
const { updateTimestamp } = useUpdateTimestamp(); const { updateTimestamp } = useUpdateTimestamp();
const mutation = useMutation({ const mutation = useMutation({
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'create-cst'], mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'create-constituenta'],
mutationFn: rsformsApi.createConstituenta, mutationFn: rsformsApi.createConstituenta,
onSuccess: data => { onSuccess: data => {
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey, data.schema); client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey, data.schema);

View File

@ -11,7 +11,7 @@ export const useDeleteConstituents = () => {
const client = useQueryClient(); const client = useQueryClient();
const { updateTimestamp } = useUpdateTimestamp(); const { updateTimestamp } = useUpdateTimestamp();
const mutation = useMutation({ const mutation = useMutation({
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'delete-multiple-cst'], mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'delete-constituents'],
mutationFn: rsformsApi.deleteConstituents, mutationFn: rsformsApi.deleteConstituents,
onSuccess: data => { onSuccess: data => {
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data); client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);

View File

@ -11,7 +11,7 @@ export const useMoveConstituents = () => {
const client = useQueryClient(); const client = useQueryClient();
const { updateTimestamp } = useUpdateTimestamp(); const { updateTimestamp } = useUpdateTimestamp();
const mutation = useMutation({ const mutation = useMutation({
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'move-cst'], mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'move-constituents'],
mutationFn: rsformsApi.moveConstituents, mutationFn: rsformsApi.moveConstituents,
onSuccess: data => { onSuccess: data => {
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data); client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);

View File

@ -11,7 +11,7 @@ export const useRenameConstituenta = () => {
const client = useQueryClient(); const client = useQueryClient();
const { updateTimestamp } = useUpdateTimestamp(); const { updateTimestamp } = useUpdateTimestamp();
const mutation = useMutation({ const mutation = useMutation({
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'rename-cst'], mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'rename-constituenta'],
mutationFn: rsformsApi.renameConstituenta, mutationFn: rsformsApi.renameConstituenta,
onSuccess: data => { onSuccess: data => {
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey, data.schema); client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.schema.id }).queryKey, data.schema);

View File

@ -11,7 +11,7 @@ export const useSubstituteConstituents = () => {
const client = useQueryClient(); const client = useQueryClient();
const { updateTimestamp } = useUpdateTimestamp(); const { updateTimestamp } = useUpdateTimestamp();
const mutation = useMutation({ const mutation = useMutation({
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'substitute-cst'], mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'substitute-constituents'],
mutationFn: rsformsApi.substituteConstituents, mutationFn: rsformsApi.substituteConstituents,
onSuccess: data => { onSuccess: data => {
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data); client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);

View File

@ -11,7 +11,7 @@ export const useUpdateConstituenta = () => {
const client = useQueryClient(); const client = useQueryClient();
const { updateTimestamp } = useUpdateTimestamp(); const { updateTimestamp } = useUpdateTimestamp();
const mutation = useMutation({ const mutation = useMutation({
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'update-cst'], mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'update-constituenta'],
mutationFn: rsformsApi.updateConstituenta, mutationFn: rsformsApi.updateConstituenta,
onSuccess: (_, variables) => { onSuccess: (_, variables) => {
updateTimestamp(variables.itemID); updateTimestamp(variables.itemID);