Built during an intensive 2-week Software Production Internship at Shandong University of Science and Technology — a closed-loop personal finance assistant that puts data integrity and enterprise architecture above floating-point shortcuts.
- 🌟 Overview
- ✨ Key Features
- 🛠️ Tech Stack
- 🏗️ Architecture
- 📂 Project Structure
- 💻 Installation & Setup
- 🔌 API Surface
- 🧪 Testing
- 🚀 Roadmap
- 👨💻 Author
- 📄 License
Book-it-quick is a full-stack, enterprise-grade personal financial management system designed from the ground up to eliminate floating-point arithmetic errors and provide users with a highly intuitive, secure, and automated dashboard for tracking income, expenses, savings goals, and passive subscriptions.
The project was developed end-to-end during a fast-paced, two-week Software Production Internship at Shandong University of Science and Technology, where the goal was to ship a closed-loop financial assistant — capture, classify, visualize, and automate — backed by production-quality patterns (stateless auth, soft delete, scheduled jobs, RBAC, and a clean REST boundary).
| Pillar | What it means in practice |
|---|---|
| Data Integrity | BigDecimal end-to-end, DECIMAL(15,2) in MySQL, no double anywhere near money. |
| Security by Default | BCrypt hashing, JWT access tokens, hashed & rotated refresh tokens, denylist, rate limiting, RBAC. |
| Automation | Spring @Scheduled jobs for recurring bills, currency sync, trash cleanup, and token pruning. |
| User-First UX | Element Plus, dark mode that persists, 5-locale i18n, real-time ECharts dashboards. |
| Enterprise Hygiene | Soft-delete + Recycle Bin, idempotent migrations, structured logging, layered exceptions. |
- Stateless JWT authentication with
io.jsonwebtoken(HS256), access tokens carrying a uniquejtiand the user's RBACroleclaim. - Refresh Token Rotation — long-lived, opaque, server-stored as
SHA-256 hashes. Every refresh issues a new pair and chains the old one
via
replaced_by, so a stolen token is good for one request only. - Access-Token Denylist —
t_token_denylistallows explicit revocation (logout, anomaly, admin force-logout) without sacrificing the stateless API. Rows self-prune once the original access token would have naturally expired (TokenCleanupScheduler). - BCrypt password hashing (Spring Security default cost factor).
- Login Rate Limiting —
LoginRateLimitServicethrottles brute-force attempts per IP + username pair with a sliding window. - RBAC —
USER/ADMINroles, gated with@PreAuthorize("hasRole('ADMIN')")on admin-only routes. First registered user is auto-promoted toADMINby the bootstrap migration.
- High-Precision Ledger — every monetary field is
DECIMAL(15,2)in MySQL andBigDecimalin Java; zero floating-point drift across aggregation, conversion, and serialization. - Comprehensive Bill Management — full CRUD with pagination,
category filtering, date-range queries, and
GET /api/bills/pagebacked by an idempotent composite index on(user_id, bill_date). - Recurring Bill Templates — users define monthly templates
(Netflix, rent, salary…) and
RecurringBillSchedulerposts them automatically on the configuredday_of_month(clamped 1–28). - Monthly Budgets — per-user spending target with real-time
burn-down reporting in the
Budgetview. - Smart Categorization — hybrid model: immutable system defaults
- per-user custom categories with a unique
(user_id, type, name)constraint.
- per-user custom categories with a unique
-
Multi-Currency Support —
useCurrencycomposable +CurrencySchedulerkeep aUSD → *exchange-rate table fresh; users flip their display currency in the UI and all dashboards recompute instantly. -
Automated Sync — daily cron at
11:00 Asia/Jakartaupserts supported codes fromexchangerate-api.com, with a defensive try/catch so a single network blip never kills the job. -
Internationalization (i18n) —
vue-i18nwith 5 fully translated locales, switched at runtime via aLanguageSelectorcomponent:Locale File 🇬🇧 English src/i18n/locales/en.json🇮🇩 Indonesian src/i18n/locales/id.json🇯🇵 Japanese src/i18n/locales/ja.json🇨🇳 Simplified Chinese src/i18n/locales/zh-CN.json🇹🇼 Traditional Chinese src/i18n/locales/zh-TW.json
- Real-Time ECharts — interactive pie charts for category distribution and line charts for income/expense trends.
- Analytics View — time-series aggregations, top-spend categories, and net cash-flow at a glance.
- Dashboard — KPI cards for income, expenses, balance, and active recurring bills, all in the user's selected currency.
- Trash / Recycle Bin (Soft Delete) — every "delete" is an
UPDATE … SET is_deleted = 1; every read filtersis_deleted = 0. TheTrashview lets users restore deleted bills, categories, and recurring templates. - TrashScheduler — nightly cleanup that hard-deletes rows whose
deleted_atis older than the retention window, keeping the table bounded. - TokenCleanupScheduler — purges expired refresh tokens and
denylist entries past their natural
expires_at. - Idempotent Migrations —
db-migration.sqlusesINFORMATION_SCHEMAguards so it can be re-run safely on any MySQL 5.7+ / 8.x instance. - Layered Error Handling —
@RestControllerAdviceGlobalExceptionHandlermaps validation, auth, and domain errors to consistent JSON envelopes.
- Dynamic Theming (Dark Mode) — a
useThemecomposable drives a pure-CSS-variable theming engine; theThemeTogglecomponent flips between light and dark and persists the choice inlocalStorage. - Element Plus UI library for accessible, well-tested components (tables, forms, dialogs, date pickers).
- Pinia for reactive, type-friendly state management across auth, currency, theme, and bill lists.
| Layer | Technology | Version |
|---|---|---|
| Framework | Vue 3 (Composition API) | ^3.5.39 |
| Build Tool | Vite | ^8.1.1 |
| UI Library | Element Plus | ^2.4.4 |
| State | Pinia | ^4.0.2 |
| Routing | Vue Router | ^4.6.4 |
| i18n | Vue I18n | ^9.14.5 |
| HTTP | Axios | ^1.7.2 |
| Charts | ECharts | ^5.4.3 |
| Testing | Vitest + @vue/test-utils + jsdom |
4.x |
| Layer | Technology | Version |
|---|---|---|
| Language | Java | 25 |
| Framework | Spring Boot | 3.5.16 |
| Security | Spring Security + JWT (io.jsonwebtoken) |
bundled |
| Validation | Spring Boot Starter Validation | bundled |
| ORM | MyBatis | 3.0.5 |
| Scheduling | Spring @Scheduled (@EnableScheduling) |
bundled |
| Build | Maven (with ./mvnw wrapper) |
bundled |
| Layer | Technology | Notes |
|---|---|---|
| RDBMS | MySQL 5.7+ / 8.x |
utf8mb4_unicode_ci everywhere |
| Money types | DECIMAL(15,2) / DECIMAL(20,8) |
BigDecimal in Java |
| Timezone | serverTimezone=Asia/Jakarta |
matches scheduler zone |
mvnwwrapper for reproducible Maven builds- Vitest with v8 coverage for the frontend
api-test.httpfor end-to-end REST smoke tests (IntelliJ HTTP client)- Lombok for boilerplate-free DTOs/Mappers
┌────────────────────────────────────────────────────────────────────────┐
│ Browser (Vue 3 SPA) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Dashboard │ │ Bills │ │Analytics │ │ Trash │ │ Saving │ │
│ │ View │ │ View │ │ View │ │ View │ │ Goals │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ └───────────┴────── Pinia stores (auth, theme, currency) ───────┤
│ │ Axios + JWT Bearer │
└──────────────────────────────┼─────────────────────────────────────────┘
│ HTTPS / JSON
▼
┌────────────────────────────────────────────────────────────────────────┐
│ Spring Boot 3.5 REST API (port 8080) │
│ │
│ SecurityFilterChain → JwtAuthenticationFilter → Rate Limiter │
│ │ │
│ ┌──────────────────┐ ┌──────┴───────┐ ┌──────────────────┐ │
│ │ AuthController │ │ BillController│ │ SavingGoalCtrl │ │
│ │ CategoryCtrl │ │ TrashCtrl │ │ AdminCtrl │ │
│ └────────┬─────────┘ └──────┬───────┘ └────────┬─────────┘ │
│ └──────────────────┬┴───────────────────┘ │
│ │ │
│ GlobalExceptionHandler (RFC-7807-style envelopes) │
│ │ │
│ ┌──────────────────────┐ │ ┌──────────────────────────────┐ │
│ │ MyBatis Mappers │ │ │ @Scheduled Cron Jobs │ │
│ │ (User, Bill, Goal, │ │ │ • CurrencyScheduler 11:00 │ │
│ │ Category, Token…) │ │ │ • RecurringBillSched 00:05 │ │
│ └──────────┬───────────┘ │ │ • TrashScheduler 03:00 │ │
│ │ │ │ • TokenCleanupSched 04:00 │ │
└──────────────┼───────────────┘ └──────────────────────────────┘ │
▼ │
┌────────────────────────────────────────────────────────────────────────┐
│ MySQL (db_bookkeeping) │
│ t_user • t_bill • t_category • t_recurring_bill • t_saving_goal │
│ t_refresh_token • t_token_denylist • exchange_rate │
└────────────────────────────────────────────────────────────────────────┘
Book-it-quick/
├── 📄 pom.xml # Spring Boot / MyBatis / Security
├── 📄 package.json # Vue 3 / Vite / ECharts / i18n
├── 📄 db-migration.sql # Idempotent schema + seed migrations
├── 📄 api-test.http # REST smoke tests
├── 📄 vite.config.js
│
├── 🗂️ src/main/java/.../config/ # Security, JWT, CORS, Schedulers
├── 🗂️ src/main/java/.../controller/ # REST controllers
├── 🗂️ src/main/java/.../service/ # Business logic
├── 🗂️ src/main/java/.../mapper/ # MyBatis mappers (XML in resources)
├── 🗂️ src/main/java/.../entity/ # JPA-style entities
├── 🗂️ src/main/java/.../dto/ # Request/response DTOs
├── 🗂️ src/main/java/.../security/ # Filters, UserDetails, entry points
│
├── 🗂️ src/views/ # Vue pages
│ ├── Dashboard.vue
│ ├── Bills.vue
│ ├── Analytics.vue
│ ├── Budget.vue
│ ├── Categories.vue
│ ├── SavingGoals.vue
│ ├── Trash.vue
│ ├── Login.vue
│ └── Register.vue
│
├── 🗂️ src/components/ # Reusable UI (ThemeToggle,
│ │ # LanguageSelector, charts…)
├── 🗂️ src/composables/ # useTheme.js, useCurrency.js
├── 🗂️ src/stores/ # Pinia stores
├── 🗂️ src/router/ # Vue Router + auth guards
├── 🗂️ src/i18n/locales/ # en / id / ja / zh-CN / zh-TW
├── 🗂️ src/utils/ # Axios instance, formatters
├── 🗂️ src/assets/ # Images, fonts
└── 🗂️ src/test/ # Vitest unit tests
Make sure the following are installed and on your PATH:
| Tool | Version |
|---|---|
| JDK | 25 |
| Maven | 3.9+ (or just use the bundled ./mvnw) |
| Node.js | v18+ (LTS recommended) |
| npm | v9+ (bundled with Node) |
| MySQL | 5.7+ / 8.x |
-
Create the database (the migration script targets the currently selected DB, so name it however you like — the default is
db_bookkeeping):CREATE DATABASE db_bookkeeping CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-
Apply the migration — the file is idempotent and safe to re-run:
mysql -u root -p db_bookkeeping < db-migration.sqlThis single script creates / patches every table the app needs:
t_user,t_bill,t_category,t_recurring_bill,t_saving_goal,t_refresh_token,t_token_denylist,exchange_rate, plus theis_deletedsoft-delete columns, the(user_id, bill_date)index, and theroleRBAC column. -
Configure credentials & API keys in
src/main/resources/application.yml:spring: datasource: url: jdbc:mysql://localhost:3306/db_bookkeeping?useSSL=false&serverTimezone=Asia/Jakarta username: root password: YOUR_DB_PASSWORD app: currency: api-key: YOUR_EXCHANGERATE_API_KEY security: jwt: secret: CHANGE_ME_TO_A_LONG_RANDOM_STRING
From the project root:
# Build once (downloads dependencies, compiles)
./mvnw clean install
# Run the API
./mvnw spring-boot:runThe backend will start on http://localhost:8080. On first boot the
first user registered via /api/auth/register is automatically promoted
to ADMIN by the migration's bootstrap step, so you can immediately
exercise the admin-only endpoints.
🪟 Windows users: use
mvnw.cmdinstead of./mvnw.
Open a second terminal in the project root:
# Install dependencies
npm install
# Start the Vite dev server
npm run devThe SPA will be served on http://localhost:5173. Vite proxies API
calls to the Spring Boot backend (see vite.config.js) so no CORS
workarounds are required in development.
For a production bundle:
npm run build # Outputs static assets to dist/
npm run preview # Locally preview the production buildThe backend exposes a clean, versioned REST API under /api. All
non-/api/auth/** endpoints require a valid Authorization: Bearer <jwt>
header.
| Domain | Endpoints | Purpose |
|---|---|---|
| Auth | POST /api/auth/register, POST /api/auth/login, POST /api/auth/refresh, POST /api/auth/logout |
Identity, refresh rotation, denylist writes |
| Bills | GET /api/bills, GET /api/bills/page, POST /api/bills, PUT /api/bills/{id}, DELETE /api/bills/{id} |
CRUD on financial transactions |
| Recurring | GET /api/recurring, POST /api/recurring, PUT /api/recurring/{id}, DELETE /api/recurring/{id} |
Monthly auto-posting templates |
| Budget | GET /api/budget, PUT /api/budget |
Per-user monthly spending cap |
| Stats | GET /api/stats/** |
Aggregations feeding the ECharts dashboards |
| Categories | GET /api/categories, POST /api/categories, PUT /api/categories/{id}, DELETE /api/categories/{id} |
User-defined transaction categories |
| Saving Goals | GET /api/saving-goals, POST /api/saving-goals, PUT /api/saving-goals/{id}, DELETE /api/saving-goals/{id} |
Wishlist / target savings tracker |
| Trash | GET /api/trash/**, POST /api/trash/{id}/restore, DELETE /api/trash/{id} |
Recycle bin: list, restore, hard-delete |
| Exchange Rates | GET /api/exchange-rates, GET /api/exchange-rates/{code} |
USD-based rates, refreshed by CurrencyScheduler |
| Admin (RBAC) | GET /api/admin/users, PUT /api/admin/users/{id}/role |
@PreAuthorize("hasRole('ADMIN')") |
📄 For concrete request/response payloads, the repository ships with
an api-test.http file — open it in IntelliJ IDEA's HTTP Client
or any compatible client to smoke-test every endpoint.
| Layer | Command | What it covers |
|---|---|---|
| Frontend (unit) | npm test |
Vitest single run |
| Frontend (watch) | npm run test:watch |
Vitest interactive watch mode |
| Frontend (coverage) | npm run test:coverage |
v8 coverage report |
| Backend (manual) | Open api-test.http in IntelliJ |
End-to-end REST flows incl. auth + RBAC |
✅ The project is structured for easy extension with
@SpringBootTestintegration tests on the service layer — see theservice/package seams for entry points.
- Bank-feed / CSV import (OFX/QIF) for automatic transaction ingestion
- Two-factor authentication (TOTP) for
ADMINaccounts - Email + push notifications when budget thresholds are crossed
- Predictive cash-flow forecasting using simple time-series models
- Docker Compose for one-command stack bootstrap (MySQL + backend + frontend)
- OpenAPI 3.1 spec auto-generated from controller signatures
Dio Stania Adinata Software Engineer — Game Technology & Software Engineering
Built during the Software Production Internship at Shandong University of Science and Technology.
- 🌐 Portfolio: [add your link]
- 💼 LinkedIn: [add your link]
- 🐙 GitHub: github.com/Rytsia1
This project is released under the MIT License — see LICENSE for
the full text. You are free to use, modify, and distribute it for
personal or commercial purposes, provided the original copyright is
retained.
If you found this project useful, consider giving it a ⭐ — it helps more than you know!
Made with ☕, a lot of BigDecimal, and a deep respect for the audit trail.