Archived
feat: uv config other feats
- add uv configuration for the backend - update frontend to make auth work - add new auth endpoints - add bookmars feat - add reader feat
This commit is contained in:
@@ -1,6 +1,25 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
import logging
|
||||
|
||||
from apps.books.models import Book, BookChapter, EBook, ReadingProgress, ReadingStatus, DownloadRecord
|
||||
from apps.books.services.metadata import enrich_ebook_metadata
|
||||
from apps.books.services.process_ebook import apply_processing_to_ebook
|
||||
from apps.reader.models import ReadingSettings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class BookReadingSettingsSerializer(serializers.ModelSerializer):
|
||||
font_style = serializers.CharField(source="font_family")
|
||||
|
||||
class Meta:
|
||||
model = ReadingSettings
|
||||
fields = ["font_size", "font_style", "background_color"]
|
||||
|
||||
def validate_font_size(self, value: int) -> int:
|
||||
if value < 12 or value > 36:
|
||||
raise serializers.ValidationError("Font size must be between 12 and 36.")
|
||||
return value
|
||||
|
||||
|
||||
class BookChapterSerializer(serializers.ModelSerializer):
|
||||
@@ -51,10 +70,14 @@ class EBookListSerializer(serializers.ModelSerializer):
|
||||
filename = serializers.CharField(read_only=True)
|
||||
format = serializers.CharField(read_only=True)
|
||||
progress = serializers.SerializerMethodField()
|
||||
started = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = EBook
|
||||
fields = ["id", "title", "author", "filename", "format", "page_count", "file_size", "cover_image", "created_at", "progress"]
|
||||
fields = [
|
||||
"id", "title", "author", "filename", "format", "page_count", "file_size",
|
||||
"cover_image", "created_at", "progress", "started",
|
||||
]
|
||||
|
||||
def get_progress(self, obj):
|
||||
try:
|
||||
@@ -62,16 +85,30 @@ class EBookListSerializer(serializers.ModelSerializer):
|
||||
except ReadingProgress.DoesNotExist:
|
||||
return None
|
||||
|
||||
def get_started(self, obj):
|
||||
"""True when the user has opened the reader and has a saved EPUB location."""
|
||||
try:
|
||||
rp = obj.reading_progress
|
||||
except ReadingProgress.DoesNotExist:
|
||||
return False
|
||||
if rp.current_position >= 99:
|
||||
return False
|
||||
return bool((rp.epub_location or "").strip())
|
||||
|
||||
|
||||
class EBookDetailSerializer(serializers.ModelSerializer):
|
||||
filename = serializers.CharField(read_only=True)
|
||||
format = serializers.CharField(read_only=True)
|
||||
file_url = serializers.SerializerMethodField()
|
||||
progress = serializers.SerializerMethodField()
|
||||
metadata = serializers.JSONField(source="metadata_json", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = EBook
|
||||
fields = ["id", "title", "author", "filename", "format", "page_count", "file_size", "file_url", "cover_image", "created_at", "updated_at", "progress"]
|
||||
fields = [
|
||||
"id", "title", "author", "filename", "format", "page_count", "file_size",
|
||||
"file_url", "cover_image", "metadata", "created_at", "updated_at", "progress",
|
||||
]
|
||||
|
||||
def get_file_url(self, obj):
|
||||
request = self.context.get("request")
|
||||
@@ -82,7 +119,11 @@ class EBookDetailSerializer(serializers.ModelSerializer):
|
||||
def get_progress(self, obj):
|
||||
try:
|
||||
rp = obj.reading_progress
|
||||
return {"current_position": rp.current_position, "last_page": rp.last_page}
|
||||
return {
|
||||
"current_position": rp.current_position,
|
||||
"last_page": rp.last_page,
|
||||
"epub_location": rp.epub_location,
|
||||
}
|
||||
except ReadingProgress.DoesNotExist:
|
||||
return None
|
||||
|
||||
@@ -103,22 +144,35 @@ class EBookUploadSerializer(serializers.ModelSerializer):
|
||||
return value
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data["user"] = self.context["request"].user
|
||||
# Auto-detect format from file extension
|
||||
import os
|
||||
|
||||
validated_data["user"] = self.context["request"].user
|
||||
name = str(getattr(validated_data.get("file"), "name", ""))
|
||||
ext = os.path.splitext(name)[1].lower().lstrip(".")
|
||||
if ext:
|
||||
validated_data["format"] = ext
|
||||
return super().create(validated_data)
|
||||
ebook = super().create(validated_data)
|
||||
try:
|
||||
apply_processing_to_ebook(ebook)
|
||||
except Exception:
|
||||
logger.exception("E-book processing failed for ebook %s", ebook.pk)
|
||||
try:
|
||||
enrich_ebook_metadata(ebook)
|
||||
except Exception:
|
||||
logger.exception("Metadata enrichment failed for ebook %s", ebook.pk)
|
||||
ebook.refresh_from_db()
|
||||
return ebook
|
||||
|
||||
|
||||
class ReadingProgressSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ReadingProgress
|
||||
fields = ["current_position", "last_page", "device_id", "device_name", "version", "updated_at"]
|
||||
fields = [
|
||||
"current_position", "last_page", "epub_location",
|
||||
"device_id", "device_name", "version", "updated_at",
|
||||
]
|
||||
read_only_fields = ["version", "updated_at"]
|
||||
extra_kwargs = {"current_position": {"required": True, "min_value": 0.0, "max_value": 100.0}}
|
||||
extra_kwargs = {"current_position": {"required": False, "min_value": 0.0, "max_value": 100.0}}
|
||||
|
||||
def validate_current_position(self, value):
|
||||
if value < 0.0 or value > 100.0:
|
||||
@@ -155,7 +209,11 @@ class DownloadRecordSerializer(serializers.ModelSerializer):
|
||||
def get_progress(self, obj):
|
||||
try:
|
||||
rp = obj.ebook.reading_progress
|
||||
return {"current_position": rp.current_position, "last_page": rp.last_page}
|
||||
return {
|
||||
"current_position": rp.current_position,
|
||||
"last_page": rp.last_page,
|
||||
"epub_location": rp.epub_location,
|
||||
}
|
||||
except ReadingProgress.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user