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)