# ============================================================ # File: db/repository.py # Purpose: # High-level BookScraper database interface. # This is the ONLY module Celery tasks and Flask should use. # # New additions for INIT-flow: # - register_book() # - update_book_after_full_scrape() # - get_registered_books() # - get_active_books() # # Existing functions remain unchanged for backward compatibility. # ============================================================ from db.db import ( upsert_book, _raw_get_book, _raw_get_all_books, get_db, ) # ------------------------------------------------------------ # FETCH OPERATIONS # ------------------------------------------------------------ def fetch_book(book_id): """Return a single book dict or None.""" return _raw_get_book(book_id) def fetch_all_books(): """Return all books ordered newest → oldest.""" return _raw_get_all_books() # ============================================================ # NEW — INIT-FLOW SUPPORT # ============================================================ def register_book(book_id, title, author=None, description=None, cover_url=None): """ Create a new book entry with initial metadata. Called when user enters a URL and presses INIT. """ fields = { "title": title, "author": author, "description": description, "cover_url": cover_url, "chapters_total": 0, "status": "registered", } upsert_book(book_id, **fields) def update_book_after_full_scrape( book_id, title=None, author=None, description=None, cover_url=None, chapters_total=None, ): """ Called after a FULL scrape when chapters are known. Moves the book into 'active' state. """ fields = {} if title is not None: fields["title"] = title if author is not None: fields["author"] = author if description is not None: fields["description"] = description if cover_url is not None: fields["cover_url"] = cover_url if chapters_total is not None: fields["chapters_total"] = chapters_total fields["status"] = "active" upsert_book(book_id, **fields) def get_registered_books(): """ Return books registered but not yet scraped. """ conn = get_db() cur = conn.execute( """SELECT * FROM books WHERE status='registered' ORDER BY created_at DESC""" ) return [dict(row) for row in cur.fetchall()] def get_active_books(): """ Return books currently in progress. """ conn = get_db() cur = conn.execute( """SELECT * FROM books WHERE status IN ('active', 'downloading') ORDER BY created_at DESC""" ) return [dict(row) for row in cur.fetchall()] # ------------------------------------------------------------ # BOOK CREATION / METADATA (existing) # ------------------------------------------------------------ def create_or_update_book( book_id, title=None, author=None, chapters_total=None, cover_url=None, cover_path=None, status=None, ): fields = {} if title is not None: fields["title"] = title if author is not None: fields["author"] = author if chapters_total is not None: fields["chapters_total"] = chapters_total if cover_url is not None: fields["cover_url"] = cover_url if cover_path is not None: fields["cover_path"] = cover_path if status is not None: fields["status"] = status if fields: upsert_book(book_id, **fields) # ------------------------------------------------------------ # STATUS MANAGEMENT (existing) # ------------------------------------------------------------ def set_status(book_id, status): upsert_book(book_id, status=status) # ------------------------------------------------------------ # INCREMENTING COUNTERS (existing — backward compat only) # ------------------------------------------------------------ def inc_downloaded(book_id, amount=1): book = _raw_get_book(book_id) if not book: return cur = book.get("downloaded", 0) or 0 upsert_book(book_id, downloaded=cur + amount) def inc_parsed(book_id, amount=1): book = _raw_get_book(book_id) if not book: return cur = book.get("parsed", 0) or 0 upsert_book(book_id, parsed=cur + amount) def inc_audio_done(book_id, amount=1): book = _raw_get_book(book_id) if not book: return cur = book.get("audio_done", 0) or 0 upsert_book(book_id, audio_done=cur + amount)