diora-web/accounts/sync.py

83 lines
3.3 KiB
Python
Raw Permalink Normal View History

from django.db.models import Max
from django.http import JsonResponse
from django.utils import timezone
from django.views.decorators.http import require_http_methods
from books.models import EBook, EBookProgress, EBookHighlights, EBookBookmarks
from podcasts.models import PodcastFeed, EpisodeProgress, PodcastQueue
from radio.models import SavedStation
@require_http_methods(['GET'])
def sync_snapshot(request):
"""Aggregate read-only snapshot of a user's data for local/native clients
(see accounts/middleware.py for the Bearer-token auth that makes this
reachable without a browser session).
Deliberately excludes book file bytes and podcast audio those stay
large/expensive and are fetched lazily via the existing per-resource
endpoints (e.g. GET /books/<id>/data/), which this same token also
unlocks. Writes (progress, highlights, bookmarks, ...) go through those
existing endpoints too, to reuse their merge semantics rather than
duplicating it here."""
if not request.user.is_authenticated:
return JsonResponse({'error': 'authentication required'}, status=401)
user = request.user
books = list(EBook.objects.filter(user=user).values('id', 'meta_ct', 'meta_iv', 'uploaded_at', 'is_read'))
for b in books:
b['uploaded_at'] = b['uploaded_at'].isoformat()
book_progress = list(
EBookProgress.objects.filter(user=user).values('book_id', 'scroll_fraction', 'position_anchor', 'updated_at')
)
for p in book_progress:
p['updated_at'] = p['updated_at'].isoformat()
book_highlights = list(EBookHighlights.objects.filter(user=user).values('book_id', 'ct', 'iv', 'updated_at'))
for h in book_highlights:
h['updated_at'] = h['updated_at'].isoformat()
book_bookmarks = list(EBookBookmarks.objects.filter(user=user).values('book_id', 'ct', 'iv', 'updated_at'))
for bm in book_bookmarks:
bm['updated_at'] = bm['updated_at'].isoformat()
podcast_feeds = list(
PodcastFeed.objects.filter(user=user)
.annotate(latest_episode_at=Max('episodes__pub_date'))
.values('id', 'title', 'artwork_url', 'rss_url', 'last_refreshed_at', 'author', 'added_at', 'auto_queue', 'latest_episode_at')
)
for f in podcast_feeds:
for key in ('last_refreshed_at', 'added_at', 'latest_episode_at'):
if f[key]:
f[key] = f[key].isoformat()
episode_progress = list(
EpisodeProgress.objects.filter(user=user)
.values('episode_id', 'position_seconds', 'played', 'dismissed', 'updated_at')
)
for ep in episode_progress:
ep['updated_at'] = ep['updated_at'].isoformat()
podcast_queue = list(
PodcastQueue.objects.filter(user=user).order_by('position').values('episode_id', 'position')
)
saved_stations = list(
SavedStation.objects.filter(user=user)
.values('id', 'name', 'url', 'bitrate', 'country', 'tags', 'favicon_url', 'is_favorite', 'notes')
)
return JsonResponse({
'server_time': timezone.now().isoformat(),
'books': books,
'book_progress': book_progress,
'book_highlights': book_highlights,
'book_bookmarks': book_bookmarks,
'podcast_feeds': podcast_feeds,
'episode_progress': episode_progress,
'podcast_queue': podcast_queue,
'saved_stations': saved_stations,
})