+
+@never_cache
+class ProgressListView(ListAPIView):
+ permission_classes = [IsAuthenticated]
+ serializer_class = serializers.ProgressSerializer
+
+ def get_queryset(self):
+ return models.Progress.objects.filter(user=self.request.user).order_by('-updated_at')
+
+
+class ProgressMixin:
+ def get_object(self):
+ try:
+ return models.Progress.objects.get(user=self.request.user, book__slug=self.kwargs['slug'])
+ except models.Progress.DoesNotExist:
+ book = get_object_or_404(Book, slug=self.kwargs['slug'])
+ return models.Progress(user=self.request.user, book=book)
+
+
+
+@never_cache
+class ProgressView(ProgressMixin, RetrieveAPIView):
+ permission_classes = [IsAuthenticated]
+ serializer_class = serializers.ProgressSerializer
+
+
+@never_cache
+class TextProgressView(ProgressMixin, RetrieveUpdateAPIView):
+ permission_classes = [IsAuthenticated]
+ serializer_class = serializers.TextProgressSerializer
+
+ def perform_update(self, serializer):
+ serializer.instance.last_mode = 'text'
+ serializer.save()
+
+
+@never_cache
+class AudioProgressView(ProgressMixin, RetrieveUpdateAPIView):
+ permission_classes = [IsAuthenticated]
+ serializer_class = serializers.AudioProgressSerializer
+
+ def perform_update(self, serializer):
+ serializer.instance.last_mode = 'audio'
+ serializer.save()
+
+
+
+@never_cache
+class SyncView(ListAPIView):
+ permission_classes = [IsAuthenticated]
+ sync_id_field = 'slug'
+ sync_id_serializer_field = 'slug'
+ sync_user_field = 'user'
+
+ def get_queryset(self):
+ try:
+ timestamp = int(self.request.GET.get('ts'))
+ except:
+ timestamp = 0
+
+ timestamp = datetime.fromtimestamp(timestamp, tz=utc)
+
+ data = []
+ return self.get_queryset_for_ts(timestamp)
+
+ def get_queryset_for_ts(self, timestamp):
+ return self.model.objects.filter(
+ updated_at__gt=timestamp,
+ **{
+ self.sync_user_field: self.request.user
+ }
+ ).order_by('updated_at')
+
+ def get_instance(self, user, data):
+ sync_id = data.get(self.sync_id_serializer_field)
+ if not sync_id:
+ return None
+ return self.model.objects.filter(**{
+ self.sync_user_field: user,
+ self.sync_id_field: sync_id
+ }).first()
+
+ def post(self, request):
+ new_ids = []
+ data = request.data
+ if not isinstance(data, list):
+ raise serializers.ValidationError('Payload should be a list')
+ for item in data:
+ instance = self.get_instance(request.user, item)
+ ser = self.get_serializer(
+ instance=instance,
+ data=item
+ )
+ ser.is_valid(raise_exception=True)
+ synced_instance = self.model.sync(
+ request.user,
+ instance,
+ ser.validated_data
+ )
+ if instance is None and 'client_id' in ser.validated_data and synced_instance is not None:
+ new_ids.append({
+ 'client_id': ser.validated_data['client_id'],
+ self.sync_id_serializer_field: getattr(synced_instance, self.sync_id_field),
+ })
+ return Response(new_ids)
+
+
+class ProgressSyncView(SyncView):
+ model = models.Progress
+ serializer_class = serializers.ProgressSerializer
+
+ sync_id_field = 'book__slug'
+ sync_id_serializer_field = 'book_slug'
+
+
+class UserListSyncView(SyncView):
+ model = models.UserList
+
+ def get_serializer_class(self):
+ if self.request.version == 'v2':
+ return serializers.UserListSerializerV2
+ return serializers.UserListSerializerV3
+
+
+class UserListItemSyncView(SyncView):
+ model = models.UserListItem
+ serializer_class = serializers.UserListItemSerializer
+
+ sync_id_field = 'uuid'
+ sync_id_serializer_field = 'uuid'
+ sync_user_field = 'list__user'
+
+ def get_queryset_for_ts(self, timestamp):
+ qs = self.model.all_objects.filter(
+ updated_at__gt=timestamp,
+ **{
+ self.sync_user_field: self.request.user
+ }
+ )
+ if self.request.query_params.get('favorites'):
+ qs = qs.filter(list__favorites=True)
+ return qs.order_by('updated_at')
+
+
+class BookmarkSyncView(SyncView):
+ model = bookmarks.models.Bookmark
+ serializer_class = BookmarkSerializer
+
+ sync_id_field = 'uuid'
+ sync_id_serializer_field = 'uuid'
+
+ def get_instance(self, user, data):
+ ret = super().get_instance(user, data)
+ if ret is None:
+ if data.get('location'):
+ ret = self.model.get_by_location(user, data['location'])
+ return ret