api: refactor user synchronization API endpoint (#67901)

This commit is contained in:
Benjamin Dauvergne 2022-10-07 09:13:51 +02:00
parent ff581d6617
commit 0cb14c0138
1 changed files with 32 additions and 31 deletions

View File

@ -52,6 +52,7 @@ from rest_framework.validators import UniqueTogetherValidator
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet, ViewSet
from authentic2.apps.journal.models import reference_integer
from authentic2.compat.drf import action
from . import api_mixins, app_settings, decorators
@ -797,31 +798,27 @@ class UsersAPI(api_mixins.GetOrCreateMixinView, HookMixin, ExceptionHandlerMixin
full_known_users = serializers.BooleanField(required=False)
timestamp = serializers.DateTimeField(required=False)
def check_uuids(self, uuids, timestamp=None):
User = get_user_model()
known_uuids = User.objects.filter(uuid__in=uuids).values_list('uuid', flat=True)
unknown_uuids = set(uuids) - set(known_uuids)
modified_users_uuids = []
if timestamp:
user_ct = ContentType.objects.get_for_model(get_user_model())
user_events = Event.objects.filter(
reference_ct_ids__contains=[user_ct.id], timestamp__gt=timestamp
)
for user_event in user_events:
for reference in user_event.references:
if (
reference is not None # xxx None references in journal?!
and ContentType.objects.get_for_model(reference) == user_ct
and reference.uuid not in modified_users_uuids
and reference.uuid not in unknown_uuids
):
modified_users_uuids.append(reference.uuid)
return (known_uuids, unknown_uuids, modified_users_uuids)
def check_unknown_uuids(self, remote_uuids, users):
return set(remote_uuids) - {user.uuid for user in users}
def get_users_from_uuids(self, known_uuids):
User = get_user_model()
known_users = User.objects.filter(uuid__in=known_uuids)
return [BaseUserSerializer(user).data for user in known_users]
def check_modified_uuids(self, timestamp, users, unknown_uuids):
modified_users_uuids = set()
user_ct = ContentType.objects.get_for_model(get_user_model())
reference_ids = [reference_integer(user) for user in users]
user_events = Event.objects.filter(
models.Q(reference_ids__overlap=reference_ids) | models.Q(user__in=users),
timestamp__gt=timestamp,
)
users_pks = {user.pk: user for user in users}
for user_event in user_events:
for ct_id, instance_pk in user_event.get_reference_ids():
if (
ct_id == user_ct.pk
and instance_pk in users_pks
and users_pks[instance_pk].uuid not in modified_users_uuids
):
modified_users_uuids.add(users_pks[instance_pk].uuid)
return modified_users_uuids
@action(detail=False, methods=['post'], permission_classes=(DjangoPermission('custom_user.search_user'),))
def synchronization(self, request):
@ -830,19 +827,23 @@ class UsersAPI(api_mixins.GetOrCreateMixinView, HookMixin, ExceptionHandlerMixin
response = {'result': 0, 'errors': serializer.errors}
return Response(response, status.HTTP_400_BAD_REQUEST)
hooks.call_hooks('api_modify_serializer_after_validation', self, serializer)
known_uuids = serializer.validated_data.get('known_uuids', [])
timestamp = serializer.validated_data.get('timestamp', None)
known_uuids, unknown_uuids, modified_users_uuids = self.check_uuids(known_uuids, timestamp)
remote_uuids = serializer.validated_data.get('known_uuids', [])
users = User.objects.filter(uuid__in=remote_uuids).only('id', 'uuid')
unknown_uuids = self.check_unknown_uuids(remote_uuids, users)
data = {
'result': 1,
'unknown_uuids': unknown_uuids,
'modified_users_uuids': modified_users_uuids,
}
timestamp = serializer.validated_data.get('timestamp', None)
if timestamp:
data['modified_users_uuids'] = self.check_modified_uuids(timestamp, users, unknown_uuids)
full_known_users = serializer.validated_data.get('full_known_users', None)
if full_known_users:
if len(known_uuids) > 1000:
known_uuids = known_uuids[:1000]
data['known_users'] = self.get_users_from_uuids(known_uuids)
# reload users to get all fields
known_users = User.objects.filter(pk__in=[user.pk for user in users[:1000]])
data['known_users'] = [BaseUserSerializer(user).data for user in known_users]
hooks.call_hooks('api_modify_response', self, 'synchronization', data)
return Response(data)