BikeLog is a multi-user motorcycle logbook. It replaces a single-page, localStorage-based HTML app with a proper Spring Boot + JPA backend, secured by Google OAuth 2.0 and JWT, so multiple people can each track their own bikes: rides, fuel fill-ups, oil changes, tyre pressure, service history, and monthly/overall statistics.
Status: Actively developed, pre-1.0. Core ride/fuel tracking and the dashboard work end-to-end; several modules described in the API contract (oil changes, tyre checks, service history, oil/tyre status widgets) are still stubs. See Project Status & Roadmap below β this is intentional, so the project has a clear on-ramp for contributors.
- How it works
- Features
- Tech stack
- Project structure
- Architecture
- API overview
- Getting started
- Configuration
- Project status & roadmap
- Known issues
- Contributing
- License
- The user logs in with Google OAuth 2.0 from the frontend.
- On successful login, the backend creates the user (if new) and issues a JWT, then redirects back to the frontend with the token in the URL.
- The frontend stores the token and sends it as
Authorization: Bearer <token>on every subsequent API call. - A custom
JwtFiltervalidates the token on each request and puts the user's ID into the Spring Security context, so controllers can read the current user via@AuthenticationPrincipal. - Each user can own multiple bikes. Every ride, fuel entry, and maintenance record belongs to a bike, which belongs to a user β all queries are scoped so users can only ever see their own data.
- The bike's
currentOdois recalculated automatically after every new ride or fuel entry, as the max ofinitialOdo, the latest ride odometer, and the latest fuel-entry odometer. - Fuel mileage (km/litre) is computed retroactively: when a new fuel entry is logged, the previous entry gets its
distanceKmandmileageKmPerLitrefilled in using the odometer difference between the two fill-ups. The most recent fuel entry always hasnullmileage until the next fill-up closes the loop.
Implemented
- Google OAuth 2.0 login β JWT issuance β stateless JWT auth on every request
- Multi-bike support per user, with an "active bike" preference
- Ride logging with auto-computed distance since the last odometer reading
- Fuel fill-up logging with auto-computed litres, cumulative litres, distance-per-tank, and mileage (km/l)
- Odometer integrity checks (new entries can't be logged behind the bike's current odometer)
- Monthly dashboard: km driven, litres used, spend, riding days, best/current mileage
- Overall (all-time) stats with a month-by-month breakdown for charting
- Month picker data source (
/{bikeId}/months)
Designed but not yet implemented (see roadmap)
- Oil change tracking + oil-change-due reminder
- Tyre pressure check tracking + latest front/rear summary
- Workshop/service history
- Delete-bike (cascade delete of all its records)
| Layer | Technology |
|---|---|
| Language / runtime | Java 21 |
| Framework | Spring Boot 4.1 (Web, Security, OAuth2 Client, Data JPA) |
| Auth | Google OAuth 2.0 (login) + JWT (API access), via jjwt |
| Database | H2 (in-memory, current) β MySQL (planned, see roadmap) |
| Mapping | MapStruct (entity β DTO) |
| Build | Gradle |
| Frontend | Static HTML/CSS/JS in frontend/ (no framework/build step) |
bikelog/
βββ src/main/java/com/mybikelog/api/
β βββ config/ # SecurityConfig, CORS, OAuth2 success handler
β βββ controller/ # REST controllers (one per resource)
β βββ service/ # Business logic
β βββ repository/ # Spring Data JPA repositories
β βββ entity/ # JPA entities
β βββ dto/ # Request/response DTOs
β βββ mapper/ # MapStruct mapper interfaces
β βββ filter/ # JwtFilter
β βββ util/ # JWTUtil, NumberUtil, AppConstants
βββ src/main/resources/
β βββ application.yaml
β βββ application-oauth.yml
βββ frontend/ # Static HTML/CSS/JS client (separate app, same repo)
βββ docs/
β βββ my-bike-log-swagger.yml # Full API contract (source of truth for endpoints/DTOs)
βββ build.gradle
βββ README.md
If you're browsing the repo and don't see
frontend/ordocs/my-bike-log-swagger.ymlyet, they're being reorganized into this layout β see issues for tracking.
- Controller β Service β Repository, standard layered Spring Boot design. Controllers stay thin: they just extract the authenticated user ID (
@AuthenticationPrincipal), delegate to a service, and wrap the result in aResponseEntity. CommonServicecentralizes logic shared across services β fetching a bike scoped to its owner, and recalculatingcurrentOdoafter any ride/fuel mutation.- MapStruct (
MapperClass) handles all entity β DTO conversion, including a genericPage<E>βPageDTO<D>mapper used by every paginated list endpoint. - Security:
SecurityConfigwires upoauth2Login()for the browser-based Google login flow and a customJwtFilter(ahead ofUsernamePasswordAuthenticationFilter) for stateless API auth on every other request.CustomAuthenticationSuccessHandleris where a successful Google login turns into an app JWT and a redirect back to the frontend. - Ownership scoping: nearly every repository query is
findBy...AndUserId(...)(or via the bike,AndBikeId(...)) so cross-user data access isn't possible even if a bike/ride/entry ID is guessed.
The full contract β every endpoint, request/response schema, and validation rule β lives in docs/my-bike-log-swagger.yml. You can paste it into the Swagger Editor to browse it interactively. Highlights:
| Resource | Base path | Notes |
|---|---|---|
| Users | /users/me |
Get/patch current user profile & active bike |
| Bikes | /bikes |
CRUD for a user's bikes |
| Rides | /rides/{bikeId} |
Log/list/delete odometer readings |
| Petrol | /petrol-entries/{bikeId} |
Log/list/delete fuel fill-ups; mileage computed automatically |
| Oil Changes | /oil-changes/{bikeId} |
Planned |
| Tyre Checks | /tyre-checks/{bikeId} |
Planned |
| Services | /services/{bikeId} |
Planned |
| Dashboard & Stats | /{bikeId}/months, /{bikeId}/dashboard, /{bikeId}/overall-stats, /{bikeId}/oil-status, /{bikeId}/tyre-status |
Monthly and all-time stats; the last two are stubbed |
All endpoints except OAuth2 login require Authorization: Bearer <jwt>.
- JDK 21
- A Google OAuth 2.0 client ID/secret (Web application type, with an authorized redirect URI of
http://localhost:8080/login/oauth2/code/googlefor local dev) - (Frontend) Any static file server β e.g. VS Code's Live Server extension, since the frontend currently expects to run at
http://127.0.0.1:5500
git clone https://github.com/arshadpatel/my-bike-log.git
cd my-bike-log
export JWT_SECRET="a-long-random-string-at-least-32-bytes"
export GOOGLE_CLIENT_ID="your-google-client-id"
export GOOGLE_CLIENT_SECRET="your-google-client-secret"
./gradlew bootRunThe API will be available at http://localhost:8080. H2 is in-memory, so data resets on every restart β the H2 console is available at /h2-console for inspecting it while developing.
cd frontend
# open index.html with a static server, e.g.:
npx live-server --port=5500Log in via the "Sign in with Google" flow; the backend will redirect back to http://127.0.0.1:5500/index.html?token=... with the JWT.
| Variable | Required | Description |
|---|---|---|
JWT_SECRET |
Yes | HMAC signing key for JWTs. Use a long random string. |
GOOGLE_CLIENT_ID |
Yes | From Google Cloud Console OAuth credentials. |
GOOGLE_CLIENT_SECRET |
Yes | From Google Cloud Console OAuth credentials. |
jwt.expiration-time (in application.yaml) controls token lifetime in milliseconds; defaults to 1 hour.
This project is being open-sourced specifically to grow through contributions, so here's an honest snapshot of what's solid vs. what's a good first issue.
- Google OAuth login β JWT issuance
- Bike CRUD (create, list, get, update)
- Ride logging, listing (with month filter + pagination), deletion
- Petrol entry logging, listing, deletion, with auto-computed litres/mileage/cumulative litres
- Monthly dashboard stats
- Overall stats with monthly breakdown
- Delete bike (
DELETE /bikes/{bikeId}) β controller method exists but returnsnull; needs a service method that cascades deletes across rides/petrol/oil/tyre/service records. - Oil status (
GET /{bikeId}/oil-status) β controller returnsnull; needs theOilChangeentity/repo/service plus the due/overdue calculation described in the OpenAPI spec. - Tyre status (
GET /{bikeId}/tyre-status) β same situation, needs theTyreCheckentity/repo/service. - Oil Changes module β entity, repository, service, controller (
/oil-changes/{bikeId}) don't exist yet; only speced in the OpenAPI contract. - Tyre Checks module β same, for
/tyre-checks/{bikeId}. - Service (workshop) history module β same, for
/services/{bikeId}.
- Migrate from H2 to MySQL for real persistence (currently in-memory only, wiped on every restart).
- Global exception handling β replace scattered
RuntimeExceptions with a@ControllerAdvicethat returns theErrorResponse/ValidationErrorResponseshapes already defined in the OpenAPI contract (with correct 400/404/409 status codes). - Bean Validation on request DTOs to match the constraints already documented in the OpenAPI spec (e.g.
odomust be positive,namelength limits). - Externalize CORS/redirect URLs β
http://127.0.0.1:5500is currently hardcoded inSecurityConfigandCustomAuthenticationSuccessHandler, which blocks any non-local deployment. - Automated tests β there currently aren't any; unit tests for services and integration tests for controllers would be very welcome.
- Reorganize repo into
backend/(or keep at root) +frontend/+docs/my-bike-log-swagger.ymlas described in Project structure.
If you find issues, please open an issue β see CONTRIBUTING.md.
Contributions are very welcome β see CONTRIBUTING.md for how to get set up, coding conventions, and how issues/PRs are handled.
MIT β free to use, modify, and distribute, with attribution.