BookShare is a social reading platform where users can track books, log reading progress, write reviews, organize personal shelves, and follow friends' reading activity — all in one place.
- Features
- Architecture Overview
- Technology Stack
- Getting Started
- Application Roles
- Key Workflows
- Contributing
- License
- Contact
Books & Reading
- Browse, search, and filter the book catalog by genre, tag, and average rating
- Track reading progress per book (pages read and completion percentage)
- Mark books as complete — progress is auto-finalized
- Write and like book reviews (star ratings 0–5, rich-text content via TinyMCE)
- Organize books into built-in shelves (Currently Reading, Want to Read, Read) and custom shelves
- View your full collection organized by shelf
Social
- Send, accept, decline, and revoke friend requests
- Remove existing connections
- Browse any user's public profile: shelves, reading progress, and bio
- Real-time activity feed showing friends' reviews, progress updates, and new connections (30-day window, paginated)
Authors
- Any registered user can apply to become an author
- Authors can submit book addition requests for admin review
- Author profiles optionally link to a registered user account
Authentication & Account Management
- Standard email/password registration and login
- Google OAuth2 / OIDC login (auto-provisions new accounts)
- Password reset via tokenized email link (Gmail API, 10-minute expiry)
Admin Panel
- Full CRUD for users, books, authors, genres, and tags
- Review and approve/reject author applications
- Review and approve author-submitted book requests
- System metrics dashboard: live JVM heap, CPU, thread and disk metrics, Caffeine cache statistics, health component status
REST API
- Full JWT-authenticated REST API under
/api/v1for mobile and external clients - Covers books, shelves, feed, profiles, author applications, and account management
- OpenAPI 3.0 specification served at
/api-docs/openapi.yaml
General
- Server-side rendering with Thymeleaf; AJAX-powered fragments for feeds, shelves, and search
- Internationalization (i18n): English, French, German, Spanish, Bengali
- Dark / light mode toggle
- Async activity event system with transactional outbox pattern
- Request logging to rolling file
BookShare is a monolithic Spring Boot application with a server-rendered Thymeleaf frontend and a parallel stateless REST API. AJAX is used selectively in the MVC layer to update page fragments without full reloads (book grid, activity feed, shelf tabs, profile connection state).
Browser (Thymeleaf + Bootstrap) Mobile / API clients
│ │
▼ ▼
Spring MVC Controllers REST API Controllers (/api/v1/**)
│ JWT filter + stateless auth
└───────────┬────────────────────────┘
▼
Service Layer (business logic, caching, event publishing)
│
┌────────┴────────────┐
│ │
JPA/Hibernate Activity Outbox (transactional writes)
│ │
PostgreSQL Scheduled Processor (every 15 s)
│
Activity + FeedEntry tables
Activity & Feed pipeline:
- User actions (review, progress update, friend connection) write to
activity_outboxwithin the same transaction. ActivityOutboxProcessor(runs every 15 seconds) picks up PENDING outbox rows, persistsActivityrecords, and fans outFeedEntryrows to the actor's connections.- Feed reads are served from the
feedCaffeine cache (60-second TTL).
Caching (Caffeine):
| Cache | TTL | Notes |
|---|---|---|
books / book-lists |
12 h / 30 min | Individual books and paginated list results |
authors / author-lists |
12 h / 30 min | |
genres / genre-lists |
24 h / 24 h | |
tags / tag-lists |
24 h / 24 h | |
shelves / shelf-lists |
10 min / 5 min | Short TTL; invalidated on shelf writes |
logins |
30 min | Keyed by email and handle |
feed |
60 s | Per-user, refreshed frequently |
Cache statistics are visible on the admin Actuator dashboard.
| Layer | Technology |
|---|---|
| Backend | Java 25, Spring Boot 3.5.5, Spring MVC, Spring Data JPA / Hibernate |
| Security | Spring Security, Spring Security OAuth2 Client (Google OIDC), JJWT 0.13 (JWT for REST API) |
| Frontend | Thymeleaf + Layout Dialect, Bootstrap 5, jQuery 3.7.1, FontAwesome |
| Rich UI | TinyMCE (description editor), FilePond (image upload), Select2 (multi-select), DataTables |
| Database | PostgreSQL 17, Flyway (migrations V1–V20) |
| Caching | Caffeine (managed via Spring Cache abstraction) |
| Spring Mail + Gmail API (Google OAuth2 UserCredentials) | |
| Observability | Spring Boot Actuator, Micrometer |
| Build & QA | Gradle (version catalog), Lombok, SpotBugs |
| Infrastructure | Docker (multi-stage build, eclipse-temurin:25-jre-alpine), Docker Compose |
- Java 25+
- Gradle 8+
- PostgreSQL (or Docker — no local DB needed when using Compose)
- Git
# Clone the repository
git clone https://github.com/smzoha/bookshare.git
cd bookshareDatabase connection defaults to localhost:5432/bookshare. Override in src/main/resources/application-dev.properties:
spring.datasource.url=jdbc:postgresql://localhost:5432/bookshare
spring.datasource.username=your_db_user
spring.datasource.password=your_db_passwordStart the application:
./gradlew bootRunAccess at: http://localhost:6001
Flyway runs all migrations automatically on startup. No manual schema setup is required.
Docker Compose starts both the application and a PostgreSQL 17 instance. No local database required.
# 1. Create your environment file
cp .env.example .envEdit .env:
DATABASE_NAME=bookshare
DATABASE_USER=your_db_user
DATABASE_PASSWORD=your_db_password
# REST API — JWT signing key (Base64-encoded) and token expiry
APP_JWT_SECRET=your_base64_encoded_secret
APP_JWT_EXPIRY_MS=1800000# 2. Start all services
./deploy.sh start
# 3. Stop all services
./deploy.sh stop- App port: 6001 (host)
- PostgreSQL port: 5433 (host, mapped to 5432 inside the container)
- Logs are written to
./logs/on the host
The project ships with sample data covering 10 authors, 10 books, 8 tags, and 5 genres. This script is not run automatically by Flyway — load it manually into your database after first startup:
psql -U your_db_user -d bookshare -f src/main/resources/seed/seed_data.sqlTwo Google Cloud credentials are needed:
- Gmail API — for sending password-reset emails
- Google OAuth2 Login — for Sign in with Google
Step 1 — Create OAuth2 Credentials in Google Cloud Console:
- Go to Google Cloud Console and create or select a project.
- Enable the Gmail API: APIs & Services → Library → Gmail API → Enable.
- Configure the OAuth Consent Screen: External, fill in app name and support email, add scopes:
https://www.googleapis.com/auth/gmail.sendopenidhttps://www.googleapis.com/auth/userinfo.emailhttps://www.googleapis.com/auth/userinfo.profile
- Create an OAuth Client ID (Web application) with these Authorized Redirect URIs:
http://localhost:6001http://localhost:6001/login/oauth2/code/googlehttp://localhost:6001/oauth2/authorization/google
- Note the
client_idandclient_secret.
Step 2 — Generate a Gmail Refresh Token:
Open in your browser (replace YOUR_CLIENT_ID):
https://accounts.google.com/o/oauth2/v2/auth?client_id=YOUR_CLIENT_ID&redirect_uri=http://localhost:6001&response_type=code&scope=https://www.googleapis.com/auth/gmail.send&access_type=offline&prompt=consent
After granting access, copy the code from the redirect URL. Exchange it for tokens:
POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded
code=AUTH_CODE&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&redirect_uri=http://localhost:6001&grant_type=authorization_codeCopy refresh_token from the response.
Step 3 — Add credentials to your config:
For local Spring Boot (secret-dev.properties, see secret-dev.properties.example):
app.gmail.client.id=your_client_id
app.gmail.client.secret=your_client_secret
app.gmail.refresh.token=your_refresh_token
spring.security.oauth2.client.registration.google.client-id=${app.gmail.client.id}
spring.security.oauth2.client.registration.google.client-secret=${app.gmail.client.secret}For Docker deployment (.env):
GOOGLE_CLIENT_ID=your_client_id
GOOGLE_CLIENT_SECRET=your_client_secret
GOOGLE_REFRESH_TOKEN=your_refresh_tokenThe Google Login flow is handled automatically by Spring Security OAuth2. The manual token exchange above is only needed for the Gmail sending credential.
The API base URL is /api/v1. It uses stateless JWT authentication — no cookies or sessions.
Getting a token:
POST /api/v1/auth/token
Content-Type: application/json
{"email": "user@example.com", "password": "yourpassword"}The response body contains {"token": "<JWT>"}. Include it in subsequent requests:
Authorization: Bearer <token>
Tokens expire after 30 minutes by default (APP_JWT_EXPIRY_MS).
The full OpenAPI 3.0 specification is available at /api-docs/openapi.yaml once the application is running. When running locally with the dev profile, Swagger UI is also available at /swagger-ui/index.html. It is disabled in production.
| Role | Description |
|---|---|
USER |
Default role. Can browse, review, shelve books, track progress, and manage social connections. Can apply to become an author. |
AUTHOR |
Elevated user. Can submit new book requests for admin review. Cannot re-apply for authorship. |
MODERATOR |
Access to /manage/** routes (subset of admin). |
ADMIN |
Full access including admin panel, user management, and Actuator dashboard. |
Role assignment: new registrations and Google logins default to USER. Admins upgrade roles via the user management panel. Author applications go through an admin-approval flow.
Registering and logging in:
/login → register tab or Google button → redirected to / (authenticated home with feed).
Browsing and shelving a book:
/book/list → filter/search → /book/{id} → add to shelf via dropdown → reading progress form.
Social connections:
/profile/{handle} → Add Friend → friend receives request → accepts → both appear in each other's connections → activity events fan out to their mutual friends' feeds.
Author application:
Home page → "Apply to be an Author" → request saved → admin reviews at /admin/author/request → on approval, user role upgraded to AUTHOR and an Author entity is created.
Book submission (authors):
/author/bookRequest → fill form (title, ISBN, authors, genres, tags, description, cover image) → submitted with PENDING status → admin reviews at /admin/book.
Password reset:
/resetPasswordRequest → enter email → tokenized link sent via Gmail → /resetPassword?token=... → set new password (token expires in 10 minutes).
- Fork the repository
- Create a branch:
feat/my-featurefor features,bugfix/my-fixfor bugfixes - Commit your changes with a clear message
- Push and open a Pull Request
- Follow existing code style and package structure
- Flyway migration files must follow the naming convention
V{N}__{description}.sqland be placed in the appropriatedb/migration/{month_year}/subdirectory - New i18n strings belong in all five locale files under
src/main/resources/locale/ - Keep controller methods thin — business logic lives in the service layer
- Cache eviction must be added alongside any writes that affect cached data
This project is licensed under the GNU General Public License v2 (GPL-2.0). See the LICENSE file for the full text.
BookShare — Powered by ZedApps
GitHub: smzoha
Email: shamah.zoha@gmail.com