Add ebook reader features: highlights, bookmarks, search, settings, PDF paginated mode
- Backend: EBookHighlights and EBookBookmarks models with encrypted blob storage;
GET/POST views with size guards (700 KB / 100 KB); migration applied
- Reader header: search, settings, bookmark add/list buttons
- Font & layout settings panel (font size, line height, max width, themes for EPUB;
zoom, invert, paginated for PDF); persisted in localStorage
- Bookmarks: encrypted per-book blob, toast on add, sidebar with jump/delete
- Full-text search: EPUB TreeWalker mark injection, PDF span search; Ctrl+F / F3;
arrow key cycling; highlights re-applied on search clear
- PDF paginated mode: single-page view, tap-zone / swipe / arrow key navigation,
smart zoom (text bounding box → scale+translate canvas), auto-enable on mobile
- Progress tracking fixes: save before hiding overlay (was always writing 100%),
wait for EPUB images to load before restoring scroll, PDF paginated uses page
fraction, sendBeacon cache for unload/visibilitychange reliability
- PDF text layer disabled pending overlay rendering fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 13:08:42 +01:00
|
|
|
from django.db import models
|
|
|
|
|
from django.contrib.auth.models import User
|
|
|
|
|
|
|
|
|
|
|
2026-10-19 17:11:19 +02:00
|
|
|
class EBookManager(models.Manager):
|
|
|
|
|
def create(self, data_ct=None, data_iv=None, **kwargs):
|
|
|
|
|
"""A book, and with data_ct/data_iv its file (EBookFile) in the same go."""
|
|
|
|
|
book = super().create(**kwargs)
|
|
|
|
|
if data_ct is not None:
|
|
|
|
|
EBookFile.objects.create(book=book, data_ct=data_ct, data_iv=data_iv or '')
|
|
|
|
|
return book
|
|
|
|
|
|
|
|
|
|
|
Add ebook reader features: highlights, bookmarks, search, settings, PDF paginated mode
- Backend: EBookHighlights and EBookBookmarks models with encrypted blob storage;
GET/POST views with size guards (700 KB / 100 KB); migration applied
- Reader header: search, settings, bookmark add/list buttons
- Font & layout settings panel (font size, line height, max width, themes for EPUB;
zoom, invert, paginated for PDF); persisted in localStorage
- Bookmarks: encrypted per-book blob, toast on add, sidebar with jump/delete
- Full-text search: EPUB TreeWalker mark injection, PDF span search; Ctrl+F / F3;
arrow key cycling; highlights re-applied on search clear
- PDF paginated mode: single-page view, tap-zone / swipe / arrow key navigation,
smart zoom (text bounding box → scale+translate canvas), auto-enable on mobile
- Progress tracking fixes: save before hiding overlay (was always writing 100%),
wait for EPUB images to load before restoring scroll, PDF paginated uses page
fraction, sendBeacon cache for unload/visibilitychange reliability
- PDF text layer disabled pending overlay rendering fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 13:08:42 +01:00
|
|
|
class EBook(models.Model):
|
|
|
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='ebooks')
|
|
|
|
|
meta_ct = models.TextField() # base64 AES-GCM ciphertext of {title, author, filename}
|
|
|
|
|
meta_iv = models.CharField(max_length=32) # hex IV for metadata
|
2026-10-19 17:11:19 +02:00
|
|
|
# The file itself is in EBookFile (book.file), not here (docs/database.md).
|
Add ebook reader features: highlights, bookmarks, search, settings, PDF paginated mode
- Backend: EBookHighlights and EBookBookmarks models with encrypted blob storage;
GET/POST views with size guards (700 KB / 100 KB); migration applied
- Reader header: search, settings, bookmark add/list buttons
- Font & layout settings panel (font size, line height, max width, themes for EPUB;
zoom, invert, paginated for PDF); persisted in localStorage
- Bookmarks: encrypted per-book blob, toast on add, sidebar with jump/delete
- Full-text search: EPUB TreeWalker mark injection, PDF span search; Ctrl+F / F3;
arrow key cycling; highlights re-applied on search clear
- PDF paginated mode: single-page view, tap-zone / swipe / arrow key navigation,
smart zoom (text bounding box → scale+translate canvas), auto-enable on mobile
- Progress tracking fixes: save before hiding overlay (was always writing 100%),
wait for EPUB images to load before restoring scroll, PDF paginated uses page
fraction, sendBeacon cache for unload/visibilitychange reliability
- PDF text layer disabled pending overlay rendering fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 13:08:42 +01:00
|
|
|
uploaded_at = models.DateTimeField(auto_now_add=True)
|
2026-08-04 18:10:33 +02:00
|
|
|
is_read = models.BooleanField(default=False)
|
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
|
|
|
# The cover, end-to-end encrypted like the rest (docs/library.md): a client that has
|
|
|
|
|
# the file extracts it, encrypts it with the user's key and sends it once.
|
|
|
|
|
cover_ct = models.TextField(blank=True, default='')
|
|
|
|
|
cover_iv = models.CharField(max_length=32, blank=True, default='')
|
Add ebook reader features: highlights, bookmarks, search, settings, PDF paginated mode
- Backend: EBookHighlights and EBookBookmarks models with encrypted blob storage;
GET/POST views with size guards (700 KB / 100 KB); migration applied
- Reader header: search, settings, bookmark add/list buttons
- Font & layout settings panel (font size, line height, max width, themes for EPUB;
zoom, invert, paginated for PDF); persisted in localStorage
- Bookmarks: encrypted per-book blob, toast on add, sidebar with jump/delete
- Full-text search: EPUB TreeWalker mark injection, PDF span search; Ctrl+F / F3;
arrow key cycling; highlights re-applied on search clear
- PDF paginated mode: single-page view, tap-zone / swipe / arrow key navigation,
smart zoom (text bounding box → scale+translate canvas), auto-enable on mobile
- Progress tracking fixes: save before hiding overlay (was always writing 100%),
wait for EPUB images to load before restoring scroll, PDF paginated uses page
fraction, sendBeacon cache for unload/visibilitychange reliability
- PDF text layer disabled pending overlay rendering fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 13:08:42 +01:00
|
|
|
|
2026-10-19 17:11:19 +02:00
|
|
|
objects = EBookManager()
|
|
|
|
|
|
Add ebook reader features: highlights, bookmarks, search, settings, PDF paginated mode
- Backend: EBookHighlights and EBookBookmarks models with encrypted blob storage;
GET/POST views with size guards (700 KB / 100 KB); migration applied
- Reader header: search, settings, bookmark add/list buttons
- Font & layout settings panel (font size, line height, max width, themes for EPUB;
zoom, invert, paginated for PDF); persisted in localStorage
- Bookmarks: encrypted per-book blob, toast on add, sidebar with jump/delete
- Full-text search: EPUB TreeWalker mark injection, PDF span search; Ctrl+F / F3;
arrow key cycling; highlights re-applied on search clear
- PDF paginated mode: single-page view, tap-zone / swipe / arrow key navigation,
smart zoom (text bounding box → scale+translate canvas), auto-enable on mobile
- Progress tracking fixes: save before hiding overlay (was always writing 100%),
wait for EPUB images to load before restoring scroll, PDF paginated uses page
fraction, sendBeacon cache for unload/visibilitychange reliability
- PDF text layer disabled pending overlay rendering fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 13:08:42 +01:00
|
|
|
class Meta:
|
|
|
|
|
ordering = ['uploaded_at']
|
2026-10-19 17:11:19 +02:00
|
|
|
# The list reads these from indexes (book_rows()); they made it fast while the file
|
|
|
|
|
# was still in this row (docs/database.md), and cost nothing now.
|
2026-10-19 15:37:11 +02:00
|
|
|
indexes = [
|
|
|
|
|
models.Index(fields=['user', 'uploaded_at', 'is_read'], name='ebook_list_idx'),
|
|
|
|
|
models.Index(fields=['user', 'cover_iv'], name='ebook_cover_idx'),
|
|
|
|
|
]
|
Add ebook reader features: highlights, bookmarks, search, settings, PDF paginated mode
- Backend: EBookHighlights and EBookBookmarks models with encrypted blob storage;
GET/POST views with size guards (700 KB / 100 KB); migration applied
- Reader header: search, settings, bookmark add/list buttons
- Font & layout settings panel (font size, line height, max width, themes for EPUB;
zoom, invert, paginated for PDF); persisted in localStorage
- Bookmarks: encrypted per-book blob, toast on add, sidebar with jump/delete
- Full-text search: EPUB TreeWalker mark injection, PDF span search; Ctrl+F / F3;
arrow key cycling; highlights re-applied on search clear
- PDF paginated mode: single-page view, tap-zone / swipe / arrow key navigation,
smart zoom (text bounding box → scale+translate canvas), auto-enable on mobile
- Progress tracking fixes: save before hiding overlay (was always writing 100%),
wait for EPUB images to load before restoring scroll, PDF paginated uses page
fraction, sendBeacon cache for unload/visibilitychange reliability
- PDF text layer disabled pending overlay rendering fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 13:08:42 +01:00
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return f"EBook #{self.pk} (user={self.user_id})"
|
|
|
|
|
|
|
|
|
|
|
2026-10-19 17:11:19 +02:00
|
|
|
class EBookFile(models.Model):
|
|
|
|
|
"""A book's file, encrypted like everything else: base64 AES-GCM of the EPUB/PDF bytes.
|
|
|
|
|
In a table of its own (docs/database.md): with the file in the book's row, every change of a
|
|
|
|
|
title, a folder or "gelesen" rewrote megabytes, and the list read past every file."""
|
|
|
|
|
book = models.OneToOneField(EBook, on_delete=models.CASCADE, primary_key=True, related_name='file')
|
|
|
|
|
data_ct = models.TextField()
|
|
|
|
|
data_iv = models.CharField(max_length=32)
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return f"EBookFile of #{self.book_id}"
|
|
|
|
|
|
|
|
|
|
|
2026-10-19 15:37:11 +02:00
|
|
|
def book_rows(user):
|
|
|
|
|
"""Every book of the user as {id, meta_ct, meta_iv, uploaded_at (ISO), is_read, has_cover},
|
|
|
|
|
oldest first — read without passing a book's file: meta_ct and meta_iv stand before it in
|
|
|
|
|
the row, the rest comes from the covering indexes ebook_list_idx and ebook_cover_idx."""
|
|
|
|
|
books = {b['id']: b for b in EBook.objects.filter(user=user).order_by().values('id', 'meta_ct', 'meta_iv')}
|
|
|
|
|
for book_id, at, read in EBook.objects.filter(user=user).order_by().values_list('id', 'uploaded_at', 'is_read'):
|
|
|
|
|
if book_id in books:
|
|
|
|
|
books[book_id].update(uploaded_at=at, is_read=read)
|
|
|
|
|
covered = set(EBook.objects.filter(user=user).exclude(cover_iv='').order_by().values_list('id', flat=True))
|
|
|
|
|
out = sorted((b for b in books.values() if 'uploaded_at' in b), key=lambda b: (b['uploaded_at'], b['id']))
|
|
|
|
|
for b in out:
|
|
|
|
|
b['uploaded_at'] = b['uploaded_at'].isoformat()
|
|
|
|
|
b['has_cover'] = b['id'] in covered
|
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
|
Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
(umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
(/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
(/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
|
|
|
class LibraryPrefs(models.Model):
|
|
|
|
|
"""The library's own settings, encrypted with the user's key (the folder order:
|
|
|
|
|
folder names are private, docs/library.md). One blob, replaced as a whole."""
|
|
|
|
|
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='library_prefs')
|
|
|
|
|
prefs_ct = models.TextField(blank=True, default='')
|
|
|
|
|
prefs_iv = models.CharField(max_length=32, blank=True, default='')
|
|
|
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
|
|
|
|
|
|
|
Add ebook reader features: highlights, bookmarks, search, settings, PDF paginated mode
- Backend: EBookHighlights and EBookBookmarks models with encrypted blob storage;
GET/POST views with size guards (700 KB / 100 KB); migration applied
- Reader header: search, settings, bookmark add/list buttons
- Font & layout settings panel (font size, line height, max width, themes for EPUB;
zoom, invert, paginated for PDF); persisted in localStorage
- Bookmarks: encrypted per-book blob, toast on add, sidebar with jump/delete
- Full-text search: EPUB TreeWalker mark injection, PDF span search; Ctrl+F / F3;
arrow key cycling; highlights re-applied on search clear
- PDF paginated mode: single-page view, tap-zone / swipe / arrow key navigation,
smart zoom (text bounding box → scale+translate canvas), auto-enable on mobile
- Progress tracking fixes: save before hiding overlay (was always writing 100%),
wait for EPUB images to load before restoring scroll, PDF paginated uses page
fraction, sendBeacon cache for unload/visibilitychange reliability
- PDF text layer disabled pending overlay rendering fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 13:08:42 +01:00
|
|
|
class EBookProgress(models.Model):
|
Lesestand: die zuletzt gelesene Stelle gewinnt, nicht mehr die weiteste; sw v100
docs/progress-sync.md ist der Vertrag für Server, Web, Android und Desktop.
- Server: read_at (Zeit, zu der dort gelesen wurde, Uhrfehler des Geräts per
sent_at herausgerechnet, nie nach "jetzt"), Gerät, weiteste Stelle. Ältere
Schreibvorgänge (offline nachgereicht, liegengelassener Tab) gewinnen nicht.
Apps ohne read_at: nur strikt vorwärts. Antwort = Zustand nach dem Schreiben.
/books/ mit X-Server-Now, /api/sync/ mit server_now.
- Web-Reader: Zeit einer Stelle ist der Moment, in dem man durch eigenes Tun
hinkam (Scrollen, Blättern, Tasten, Vorlesen); unverändert Liegendes
schickt nichts bzw. nur seine alte Zeit. Vor dem Wiederherstellen und
während eines Blicks (Fußnote, "Weit gescrollt") wird nicht gespeichert.
Eigene Stellen in einem Ausgang (localStorage) bis der Server sie hat.
Angebote "Auf <Gerät> weitergelesen" und "Weiteste Stelle".
- Tests: books/tests.py (Reihenfolge, Uhrfehler, Zukunft, alte Apps, weiteste
Stelle, Liste und Snapshot).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 19:31:36 +02:00
|
|
|
"""Where to continue a book: the place it was last read, on any device
|
|
|
|
|
(docs/progress-sync.md), plus the furthest place ever reached."""
|
Add ebook reader features: highlights, bookmarks, search, settings, PDF paginated mode
- Backend: EBookHighlights and EBookBookmarks models with encrypted blob storage;
GET/POST views with size guards (700 KB / 100 KB); migration applied
- Reader header: search, settings, bookmark add/list buttons
- Font & layout settings panel (font size, line height, max width, themes for EPUB;
zoom, invert, paginated for PDF); persisted in localStorage
- Bookmarks: encrypted per-book blob, toast on add, sidebar with jump/delete
- Full-text search: EPUB TreeWalker mark injection, PDF span search; Ctrl+F / F3;
arrow key cycling; highlights re-applied on search clear
- PDF paginated mode: single-page view, tap-zone / swipe / arrow key navigation,
smart zoom (text bounding box → scale+translate canvas), auto-enable on mobile
- Progress tracking fixes: save before hiding overlay (was always writing 100%),
wait for EPUB images to load before restoring scroll, PDF paginated uses page
fraction, sendBeacon cache for unload/visibilitychange reliability
- PDF text layer disabled pending overlay rendering fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 13:08:42 +01:00
|
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='ebook_progress')
|
|
|
|
|
book = models.ForeignKey(EBook, on_delete=models.CASCADE, related_name='progress')
|
|
|
|
|
scroll_fraction = models.FloatField(default=0.0)
|
2026-04-01 15:41:30 +02:00
|
|
|
position_anchor = models.CharField(max_length=30, blank=True, default='')
|
Lesestand: die zuletzt gelesene Stelle gewinnt, nicht mehr die weiteste; sw v100
docs/progress-sync.md ist der Vertrag für Server, Web, Android und Desktop.
- Server: read_at (Zeit, zu der dort gelesen wurde, Uhrfehler des Geräts per
sent_at herausgerechnet, nie nach "jetzt"), Gerät, weiteste Stelle. Ältere
Schreibvorgänge (offline nachgereicht, liegengelassener Tab) gewinnen nicht.
Apps ohne read_at: nur strikt vorwärts. Antwort = Zustand nach dem Schreiben.
/books/ mit X-Server-Now, /api/sync/ mit server_now.
- Web-Reader: Zeit einer Stelle ist der Moment, in dem man durch eigenes Tun
hinkam (Scrollen, Blättern, Tasten, Vorlesen); unverändert Liegendes
schickt nichts bzw. nur seine alte Zeit. Vor dem Wiederherstellen und
während eines Blicks (Fußnote, "Weit gescrollt") wird nicht gespeichert.
Eigene Stellen in einem Ausgang (localStorage) bis der Server sie hat.
Angebote "Auf <Gerät> weitergelesen" und "Weiteste Stelle".
- Tests: books/tests.py (Reihenfolge, Uhrfehler, Zukunft, alte Apps, weiteste
Stelle, Liste und Snapshot).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 19:31:36 +02:00
|
|
|
# When the reader was at that place (ms since epoch, server clock); null before 2026-10-12.
|
|
|
|
|
read_at = models.BigIntegerField(null=True, blank=True)
|
|
|
|
|
device = models.CharField(max_length=40, blank=True, default='')
|
|
|
|
|
furthest_fraction = models.FloatField(default=0.0)
|
|
|
|
|
furthest_anchor = models.CharField(max_length=30, blank=True, default='')
|
Add ebook reader features: highlights, bookmarks, search, settings, PDF paginated mode
- Backend: EBookHighlights and EBookBookmarks models with encrypted blob storage;
GET/POST views with size guards (700 KB / 100 KB); migration applied
- Reader header: search, settings, bookmark add/list buttons
- Font & layout settings panel (font size, line height, max width, themes for EPUB;
zoom, invert, paginated for PDF); persisted in localStorage
- Bookmarks: encrypted per-book blob, toast on add, sidebar with jump/delete
- Full-text search: EPUB TreeWalker mark injection, PDF span search; Ctrl+F / F3;
arrow key cycling; highlights re-applied on search clear
- PDF paginated mode: single-page view, tap-zone / swipe / arrow key navigation,
smart zoom (text bounding box → scale+translate canvas), auto-enable on mobile
- Progress tracking fixes: save before hiding overlay (was always writing 100%),
wait for EPUB images to load before restoring scroll, PDF paginated uses page
fraction, sendBeacon cache for unload/visibilitychange reliability
- PDF text layer disabled pending overlay rendering fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 13:08:42 +01:00
|
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
unique_together = ('user', 'book')
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return f"Progress book={self.book_id} user={self.user_id} {self.scroll_fraction:.2f}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class EBookHighlights(models.Model):
|
|
|
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='ebook_highlights')
|
|
|
|
|
book = models.ForeignKey(EBook, on_delete=models.CASCADE, related_name='highlights')
|
|
|
|
|
ct = models.TextField() # base64 AES-GCM ciphertext of JSON array
|
|
|
|
|
iv = models.CharField(max_length=32) # hex IV
|
|
|
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
unique_together = ('user', 'book')
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return f"Highlights book={self.book_id} user={self.user_id}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class EBookBookmarks(models.Model):
|
|
|
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='ebook_bookmarks')
|
|
|
|
|
book = models.ForeignKey(EBook, on_delete=models.CASCADE, related_name='bookmarks')
|
|
|
|
|
ct = models.TextField()
|
|
|
|
|
iv = models.CharField(max_length=32)
|
|
|
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
unique_together = ('user', 'book')
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return f"Bookmarks book={self.book_id} user={self.user_id}"
|