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
| Value | Meaning |
|---|---|
WANT | On the to-read list; no reading logged yet |
READING | Actively being read; startedAt is set |
FINISHED | Completed; finishedAt is set |
DROPPED | Abandoned |
LetterSize Enum
Used to estimate reading time per page when only pages are logged.
| Value | Pages/hour (client estimate) |
|---|---|
SMALL | 20 |
MEDIUM | 30 (default) |
LARGE | 40 |
Reading Pace Source
The server resolves an effective reading pace and reports its origin in readingPaceSource.
| Value | Meaning |
|---|---|
none | No pace could be resolved (e.g. finished/dropped, no pages left) |
target_book | Per-book targetPagesPerDay override |
target_user | User’s defaultReadingPagesPerDay setting |
adaptive | Computed 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.
| Method | Path | Key Params | Description |
|---|---|---|---|
GET | /books | status, query, limit (default 100), offset | List 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}/detail | — | Book + aggregates (minutes, sessions, last read, ETA) |
GET | /books/{id}/logs | — | All reading logs for a book, newest first |
GET | /books/stats | startDate, endDate (both required) | Aggregated reading stats for the user |
POST | /books | body: CreateBookDTO | Create a book (status defaults to WANT); returns 201 + book |
POST | /books/log | body: CreateReadingLogDTO | Log a reading session; auto-promotes WANT → READING |
PATCH | /books/{id} | body: UpdateBookDTO | Update 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
| File | Purpose |
|---|---|
books/books_domain/.../model/Book.kt | Domain Book model with pace/ETA fields |
books/books_domain/.../model/BookStatus.kt | WANT / READING / FINISHED / DROPPED enum |
books/books_domain/.../model/LetterSize.kt | Letter-size enum for reading-time estimates |
books/books_domain/.../model/ReadingLog.kt | Reading session model (minutes + pages) |
books/books_domain/.../model/BookDetail.kt | Book + aggregate detail model |
books/books_domain/.../model/BooksStats.kt | Stats model |
books/books_domain/.../repository/BooksRepository.kt | Repository interface |
books/books_domain/.../use_cases/BooksUseCases.kt | Use-case bundle |
books/books_data/.../repository/BooksRepositoryImpl.kt | Offline-first repository implementation |
books/books_data/.../remote/BooksKtorApi.kt | Ktor HTTP client for /books endpoints |
books/books_data/.../datasources/BookRemoteDataSource.kt | Remote data source wrapper |
books/books_data/.../local/BookDao.kt | Room DAO (Book + ReadingLog entities) |
books/books_presentation/.../ui/viewmodel/BooksViewModel.kt | Library MVI ViewModel |
books/books_presentation/.../ui/viewmodel/BookDetailViewModel.kt | Detail + logging ViewModel |
books/books_presentation/.../ui/viewmodel/BooksStatsViewModel.kt | Stats ViewModel |
books/books_presentation/.../intent/BooksIntent.kt | Intents + effects for all three screens |
books/books_presentation/.../navigation/BooksNavGraph.kt | books, books/{bookId}, books/stats routes (feature-gated) |
server/.../routing/BooksRouting.kt | 8 REST endpoints with validation |
server/.../service/BooksService.kt | Status transitions, stats, reading-pace projection |
server/.../models/books/BookDTO.kt | Server book DTO (with pace fields) |
server/.../models/books/CreateReadingLogDTO.kt | Reading-log request DTO |
Testing
- Unit:
BookDetailViewModel.logProgressdelta logic — current page behind last logged page should error unlessallowDecreaseis set;logQuickPagesReadminute estimation perLetterSize. - Unit: reading-pace resolution order (
target_book→target_user→adaptive→none) and ETA projection inBooksService.buildPaceProjection. - Integration:
POST /books/logon aWANTbook promotes it toREADINGand setsstartedAt;PATCHtoFINISHEDsetsfinishedAt. - Integration:
GET /books/statsstreak — consecutive days withminutes > 0fromendDatebackwards; a gap breaks the streak. - Offline: simulate
IOExceptionand verify reads fall back to Room and local stat computation inBooksRepositoryImpl.
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(seeBooksService.logReadingMinutes). AFINISHED/DROPPEDbook stays in place. - Confirm the
POST /books/logreturned 201 — a failure leaves the status unchanged.
Stats / streak look wrong
GET /books/statsrequires bothstartDateandendDate(YYYY-MM-DD); a missing param returns 400.- The streak counts only days with
minutes > 0, walking back fromendDate; 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
noneforFINISHED/DROPPEDbooks, books with nopages, 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
BOOKSfeature flag (RequireFeatureScreen/ serverFeatureGuard). ConfirmBOOKSis enabled for the user/platform.
Date rejected with 400
- Wire dates must be
YYYY-MM-DDserver-side (parseApiDate); the client formats withdd/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.
| Method | Path | Description |
|---|---|---|
GET | /books | List books (filterable by status, query) |
POST | /books | Create a book |
GET | /books/stats | Get reading stats for a date range |
POST | /books/log | Log reading minutes |
GET | /books/{id} | Get book by ID |
GET | /books/{id}/detail | Get detailed book info |
GET | /books/{id}/logs | Get reading logs for a book |
PATCH | /books/{id} | Update book |
DELETE | /books/{id} | Delete book |
Related Docs
- Integration — Books module integration steps and Room schema
- Desktop implementation — desktop-specific Books screen
- Habits — streak tracking and date-scoped logging patterns
- Calendar — dashboard aggregation (reading streak surfaces there)
- Domain & models — shared domain model reference