Skip to Content
FeaturesBooksFeature guide

Books Feature Guide

Guide to the Books module — a reading-tracker that manages a personal library, logs reading sessions (minutes + pages), tracks a reading streak, and projects completion dates against an optional due date.

Overview

The Books module lets users build a library of books, each with an optional page count, letter size, and due date. Reading is captured as ReadingLog sessions (minutes and/or pages on a given date). Status transitions are partly automatic: logging a session against a WANT book promotes it to READING and stamps startedAt; marking a book FINISHED stamps finishedAt. The server computes reading statistics (minutes today/week/month, streak, books finished this month, currently-reading count) and a reading-pace projection (effective pages/day, estimated completion date, estimated minutes remaining) from recent logs and per-user pace defaults.

The client is offline-first: the repository fetches from the server, caches into Room, and falls back to local Room computations when the network is unavailable. The whole feature is gated behind a BOOKS feature flag (RequireFeatureScreen / FeatureGuard).

Architecture

BooksRepositoryImpl always tries the remote source first, then mirrors results into Room via BookLocalDataSource; on IOException it serves cached data and computes stats locally.

Data Models

BookDetail wraps a Book plus aggregates (totalMinutes, sessionsCount, lastReadDate, totalPagesRead, estimatedTimeRemaining, pagesPerDay). BooksStats carries minutesToday, minutesThisWeek, minutesThisMonth, readingStreak, booksFinishedThisMonth, currentlyReadingCount.

BookStatus Enum

ValueMeaning
WANTOn the to-read list; no reading logged yet
READINGActively being read; startedAt is set
FINISHEDCompleted; finishedAt is set
DROPPEDAbandoned

LetterSize Enum

Used to estimate reading time per page when only pages are logged.

ValuePages/hour (client estimate)
SMALL20
MEDIUM30 (default)
LARGE40

Reading Pace Source

The server resolves an effective reading pace and reports its origin in readingPaceSource.

ValueMeaning
noneNo pace could be resolved (e.g. finished/dropped, no pages left)
target_bookPer-book targetPagesPerDay override
target_userUser’s defaultReadingPagesPerDay setting
adaptiveComputed from the last 14 days of reading logs

API Endpoints

All endpoints are under /books and require an authenticated user (LoggedUserDTO). Books are scoped per user.

MethodPathKey ParamsDescription
GET/booksstatus, query, limit (default 100), offsetList books, filtered by status/search, newest-updated first
GET/books/{id}Get a single book (with computed pace fields); 404 if missing
GET/books/{id}/detailBook + aggregates (minutes, sessions, last read, ETA)
GET/books/{id}/logsAll reading logs for a book, newest first
GET/books/statsstartDate, endDate (both required)Aggregated reading stats for the user
POST/booksbody: CreateBookDTOCreate a book (status defaults to WANT); returns 201 + book
POST/books/logbody: CreateReadingLogDTOLog a reading session; auto-promotes WANTREADING
PATCH/books/{id}body: UpdateBookDTOUpdate fields and/or status (drives startedAt/finishedAt)
DELETE/books/{id}Soft-delete the book (status = DELETED)

dueDate and reading-log date use YYYY-MM-DD and are validated server-side with parseApiDate (400 on bad format). The client sends dates using WIRE_DATE_FORMAT (dd/MM/yyyy). Validation: title must be non-blank, pages must be > 0, minutes must be ≥ 0, and letterSize must be one of SMALL/MEDIUM/LARGE.

State Machine

Status changes are applied in BooksService.update: moving to READING stamps startedAt only if it was null; moving to FINISHED always stamps finishedAt. Logging minutes on a WANT book (BooksService.logReadingMinutes) auto-promotes it to READING. Deletes are soft (Status.DELETED).

Client Behavior

Search filtering is local and instant: BooksViewModel.setSearchQuery filters the already-loaded list in memory, while changing the status filter triggers a fresh getBooks call.

Key Files

FilePurpose
books/books_domain/.../model/Book.ktDomain Book model with pace/ETA fields
books/books_domain/.../model/BookStatus.ktWANT / READING / FINISHED / DROPPED enum
books/books_domain/.../model/LetterSize.ktLetter-size enum for reading-time estimates
books/books_domain/.../model/ReadingLog.ktReading session model (minutes + pages)
books/books_domain/.../model/BookDetail.ktBook + aggregate detail model
books/books_domain/.../model/BooksStats.ktStats model
books/books_domain/.../repository/BooksRepository.ktRepository interface
books/books_domain/.../use_cases/BooksUseCases.ktUse-case bundle
books/books_data/.../repository/BooksRepositoryImpl.ktOffline-first repository implementation
books/books_data/.../remote/BooksKtorApi.ktKtor HTTP client for /books endpoints
books/books_data/.../datasources/BookRemoteDataSource.ktRemote data source wrapper
books/books_data/.../local/BookDao.ktRoom DAO (Book + ReadingLog entities)
books/books_presentation/.../ui/viewmodel/BooksViewModel.ktLibrary MVI ViewModel
books/books_presentation/.../ui/viewmodel/BookDetailViewModel.ktDetail + logging ViewModel
books/books_presentation/.../ui/viewmodel/BooksStatsViewModel.ktStats ViewModel
books/books_presentation/.../intent/BooksIntent.ktIntents + effects for all three screens
books/books_presentation/.../navigation/BooksNavGraph.ktbooks, books/{bookId}, books/stats routes (feature-gated)
server/.../routing/BooksRouting.kt8 REST endpoints with validation
server/.../service/BooksService.ktStatus transitions, stats, reading-pace projection
server/.../models/books/BookDTO.ktServer book DTO (with pace fields)
server/.../models/books/CreateReadingLogDTO.ktReading-log request DTO

Testing

  • Unit: BookDetailViewModel.logProgress delta logic — current page behind last logged page should error unless allowDecrease is set; logQuickPagesRead minute estimation per LetterSize.
  • Unit: reading-pace resolution order (target_booktarget_useradaptivenone) and ETA projection in BooksService.buildPaceProjection.
  • Integration: POST /books/log on a WANT book promotes it to READING and sets startedAt; PATCH to FINISHED sets finishedAt.
  • Integration: GET /books/stats streak — consecutive days with minutes > 0 from endDate backwards; a gap breaks the streak.
  • Offline: simulate IOException and verify reads fall back to Room and local stat computation in BooksRepositoryImpl.

Note: there are currently no automated tests for the Books module in server/src/test.

Troubleshooting

Book not promoted to READING after logging

  • Auto-promotion only fires when the book is currently WANT (see BooksService.logReadingMinutes). A FINISHED/DROPPED book stays in place.
  • Confirm the POST /books/log returned 201 — a failure leaves the status unchanged.

Stats / streak look wrong

  • GET /books/stats requires both startDate and endDate (YYYY-MM-DD); a missing param returns 400.
  • The streak counts only days with minutes > 0, walking back from endDate; logging pages with 0 minutes does not extend the streak.
  • Week/month windows are computed in the user’s timezone (Users.timeZone); a timezone mismatch can shift day boundaries.

No ETA / completion estimate showing

  • Pace projection returns none for FINISHED/DROPPED books, books with no pages, or books with no pages left.
  • An adaptive pace needs reading logs within the last 14 days; otherwise it falls back to the per-book or per-user target, or none.

Books screen blank or “feature unavailable”

  • The whole module is gated behind the BOOKS feature flag (RequireFeatureScreen / server FeatureGuard). Confirm BOOKS is enabled for the user/platform.

Date rejected with 400

  • Wire dates must be YYYY-MM-DD server-side (parseApiDate); the client formats with dd/MM/yyyy (WIRE_DATE_FORMAT). A mismatch surfaces as “date must be YYYY-MM-DD”.

API Endpoints

All endpoints require JWT authentication and are prefixed with /api/v1. See server/README.md for full reference.

MethodPathDescription
GET/booksList books (filterable by status, query)
POST/booksCreate a book
GET/books/statsGet reading stats for a date range
POST/books/logLog reading minutes
GET/books/{id}Get book by ID
GET/books/{id}/detailGet detailed book info
GET/books/{id}/logsGet reading logs for a book
PATCH/books/{id}Update book
DELETE/books/{id}Delete book