Archived
- add uv configuration for the backend - update frontend to make auth work - add new auth endpoints - add bookmars feat - add reader feat
125 lines
3.8 KiB
Python
125 lines
3.8 KiB
Python
import re
|
|
|
|
from rest_framework import serializers
|
|
|
|
from apps.annotations.models import Bookmark, Note
|
|
|
|
_HEX_COLOR_RE = re.compile(r"^#[0-9A-Fa-f]{6}$")
|
|
|
|
|
|
def validate_highlight_color(value: str) -> str:
|
|
stripped = (value or "").strip()
|
|
if not _HEX_COLOR_RE.match(stripped):
|
|
raise serializers.ValidationError("highlight_color must be a hex color like #fde047.")
|
|
return stripped.lower()
|
|
|
|
|
|
class BookmarkSerializer(serializers.ModelSerializer):
|
|
ebook_title = serializers.CharField(source="ebook.title", read_only=True)
|
|
|
|
class Meta:
|
|
model = Bookmark
|
|
fields = [
|
|
"id",
|
|
"ebook",
|
|
"ebook_title",
|
|
"epub_cfi",
|
|
"chapter_index",
|
|
"chapter_title",
|
|
"page",
|
|
"location_text",
|
|
"content",
|
|
"highlight_color",
|
|
"created_at",
|
|
"updated_at",
|
|
]
|
|
read_only_fields = ["id", "created_at", "updated_at", "ebook_title", "page"]
|
|
|
|
|
|
class BookmarkCreateSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Bookmark
|
|
fields = [
|
|
"ebook",
|
|
"epub_cfi",
|
|
"chapter_index",
|
|
"chapter_title",
|
|
"location_text",
|
|
"content",
|
|
"highlight_color",
|
|
]
|
|
|
|
def validate_highlight_color(self, value: str) -> str:
|
|
return validate_highlight_color(value)
|
|
|
|
def validate_epub_cfi(self, value: str) -> str:
|
|
stripped = (value or "").strip()
|
|
if not stripped:
|
|
raise serializers.ValidationError("epub_cfi is required.")
|
|
return stripped
|
|
|
|
def validate_chapter_index(self, value: int) -> int:
|
|
if value < 0:
|
|
raise serializers.ValidationError("chapter_index must be non-negative.")
|
|
return value
|
|
|
|
def validate(self, attrs):
|
|
user = self.context["request"].user
|
|
ebook = attrs["ebook"]
|
|
epub_cfi = attrs["epub_cfi"]
|
|
if Bookmark.objects.filter(user=user, ebook=ebook, epub_cfi=epub_cfi).exists():
|
|
raise serializers.ValidationError(
|
|
{"epub_cfi": "A marker already exists for this passage."}
|
|
)
|
|
return attrs
|
|
|
|
def create(self, validated_data):
|
|
validated_data["user"] = self.context["request"].user
|
|
validated_data["page"] = validated_data.get("chapter_index", 0) + 1
|
|
validated_data["content"] = (validated_data.get("content") or "").strip()
|
|
return super().create(validated_data)
|
|
|
|
|
|
class NoteSerializer(serializers.ModelSerializer):
|
|
book_title = serializers.CharField(source="book.title", read_only=True)
|
|
|
|
class Meta:
|
|
model = Note
|
|
fields = [
|
|
"id",
|
|
"book",
|
|
"book_title",
|
|
"page",
|
|
"location_text",
|
|
"content",
|
|
"created_at",
|
|
"updated_at",
|
|
]
|
|
read_only_fields = ["id", "created_at", "updated_at", "book_title"]
|
|
|
|
def validate_page(self, value: int) -> int:
|
|
if value < 1:
|
|
raise serializers.ValidationError("Page must be a positive integer.")
|
|
return value
|
|
|
|
|
|
class NoteCreateSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Note
|
|
fields = ["book", "page", "location_text", "content"]
|
|
|
|
def validate_page(self, value: int) -> int:
|
|
if value < 1:
|
|
raise serializers.ValidationError("Page must be a positive integer.")
|
|
return value
|
|
|
|
def validate_content(self, value: str) -> str:
|
|
stripped = value.strip()
|
|
if not stripped:
|
|
raise serializers.ValidationError("Note content cannot be empty.")
|
|
return stripped
|
|
|
|
def create(self, validated_data):
|
|
validated_data["user"] = self.context["request"].user
|
|
return super().create(validated_data)
|