A self-hosted, role-aware time tracker for the people who keep meaning to start one.
- What it is
- Project journey
- Features
- Architecture at a glance
- Tech stack
- Project layout
- Getting started
- Configuration
- API surface
- Testing
- Roadmap
- Contributing
- Authors
Micro Time Management is a full-stack web app for tracking how you actually spend your day. You log in, pick a date, and add activities — time blocks with a name, description, and start/end times. The backend rejects overlapping blocks, keeps them in chronological order, and scopes everything to the signed-in user. Admins can manage roles from a separate panel.
The project started as a side-project during a Master's degree and is now being finished as a portfolio piece. It is deliberately built around a real auth stack, a real role model, and real test coverage rather than a toy backend, so it doubles as a reference for anyone wiring up Spring Security + JWT sessions against MongoDB.
The bones of this app — the data model, the auth flow, RBAC, the activity logic, the React shell, and most of the early controllers and services — went in back in late 2022 during my Master's. After that the repo sat quiet for a while.
I picked it back up in early 2026 and spent a couple of months getting the foundation back in shape before adding anything new. That stretch was all solo: refactoring the auth flow, isolating the service boundaries, fixing RBAC end-to-end, building out role CRUD with pagination, getting a working user profile + update endpoint in, wiring up the login and registration screens, and giving the test suite some real display names plus a few more supporting tests.
Once the base felt solid again, I brought Claude in as a co-author to finish the rest. Pair-programming with an AI is becoming a normal part of how software gets built, and I wanted to grow into that workflow rather than work around it. I'm still the one driving the product and reviewing every change; Claude just lets me move at a pace I actually enjoy. The auth hardening, the activity tracker UI, the profile page, the admin panel, and most of the service-layer test coverage came out of that pairing.
The result is an app that's mine in design and direction, built with a workflow I'd happily reach for on the next thing too.
- 🔐 Registration & login — BCrypt-hashed passwords, JWT access (5h) + opaque refresh (7d) tokens, automatic refresh on
401. - 📅 Daily activity tracker — create, edit, delete activities per date. Overlapping time ranges and duplicates are rejected at the service layer; insertions stay chronologically sorted.
- 👤 Profile management — update name, email, username, date of birth, and change password with current-password verification.
- 🚪 Logout & session revocation — server-side session is invalidated on logout (and on re-login: single active session per user).
- 🛡️ Role management — create, rename (with name-conflict detection), and soft-delete roles from a dedicated admin panel.
- 👥 Bulk user/role operations — backend endpoints for adding/removing roles to users in bulk (UI coming soon).
- 🔎 Paginated user listing — sortable, scopable, locked behind
ROLE_MTM_ADMIN_OPS.
- 🧪 Service-layer test coverage for activities, users, roles, auth, and pagination utilities.
- 🪵 Rolling-file structured logging via Logback.
- 📓 Swagger UI auto-generated in the dev profile.
- 🔁 Single-flight 401 refresh — concurrent expired requests share one refresh round-trip in the Axios client.
- 🗂 Cross-tab auth sync in the frontend via
storageevents.
┌──────────────────────────────────────┐
│ React SPA │
│ Pages: Home · Login · Register │
│ Dashboard · Activity · │
│ Profile · Admin │
│ ProtectedRoute / AdminRoute │
│ Axios apiClient (+401 refresh) │
└──────────────────┬───────────────────┘
│ HTTPS / JSON
▼
┌─────────────────────────────────────────────────────────────┐
│ Spring Boot 3.5 API │
│ │
│ MtmSessionFilter ──► AuthN/AuthZ ──► Controllers ──► Svcs │
│ │ │ │
│ ▼ ▼ │
│ JWT validate + session DB check Domain logic │
│ │
└────────────────────────────┬────────────────────────────────┘
│ Spring Data MongoDB
▼
┌───────────────────────────────────────┐
│ MongoDB │
│ users · roles · sessions · tokens │
│ micro_activity_record │
└───────────────────────────────────────┘
- Client hits an endpoint with
Authorization: Bearer <jwt>. MtmSessionFilterparses the JWT, callsAuthenticationAndAuthorizationService.validateCurrentUserSessionForAccessToken, and looks up the access token + session in MongoDB.- Valid → the resolved
Userbecomes the Spring Security principal for the rest of the request. Expired/invalid →401. Unexpected exception →500(deliberately distinct from auth failures). - Controllers route to services, which return either DTOs or a
GenericMessageResponseDTOenvelope.
| Concern | Choice |
|---|---|
| Language | Java 17 |
| Framework | Spring Boot 3.5.10 |
| Persistence | Spring Data MongoDB |
| Security | Spring Security 6 + custom MtmSessionFilter |
| Auth tokens | jjwt 0.13.0 (HS512) + DB-backed sessions / refresh tokens |
| Validation | spring-boot-starter-validation (Bean Validation) |
| API docs | springdoc-openapi 2.8.15 (Swagger UI) |
| Build | Maven, fat-jar via spring-boot-maven-plugin |
| Logging | Logback (rolling file) |
| Testing | JUnit 5, Mockito, AssertJ |
| Concern | Choice |
|---|---|
| Framework | React 18 (Create React App) |
| Routing | React Router v6 |
| Styling | Tailwind CSS 3 (prefix mtm-) + Bootstrap 5 + Ant Design 5 |
| HTTP | Axios (shared client with request + response interceptors) |
| State | useState + a focused useAuth hook + a tiny pub/sub on top of localStorage |
| Icons | react-icons |
| Concern | Choice |
|---|---|
| Local DB | Docker Compose (MongoDB + Mongo Express) |
| Profiles | dev (port 8080, context /mtm-dev) · prod (env-var driven, locked CORS) |
MicroTimeManagement/
├── backend/api-service/ Spring Boot REST API
│ ├── src/main/java/.../apiservice/
│ │ ├── callbacks/ BeforeConvertCallback (audit fields)
│ │ ├── config/ Profile-aware Spring Security
│ │ ├── constants/ Centralised error / role / pagination constants
│ │ ├── controller/ Auth · User · Role · Activity · Admin
│ │ ├── converter/ Model ↔ DTO
│ │ ├── dto/{entity,request,response}
│ │ ├── enums/
│ │ ├── exceptions/ Custom hierarchy + @ControllerAdvice
│ │ ├── filter/ MtmSessionFilter (per-request session validation)
│ │ ├── handler/ Global exception handler
│ │ ├── model/ MongoDB documents
│ │ ├── repository/ Spring Data interfaces
│ │ ├── service/{,impl} Business logic
│ │ └── utils/ JWT + API utilities
│ └── src/test/java/.../apiservice/ Service-layer tests + test factories
├── frontend/ React 18 SPA
│ └── src/
│ ├── Pages/ Home · Login · Registration · Dashboard ·
│ │ Activity · Profile · Admin
│ ├── components/ ProtectedRoute · AdminRoute · NavigationBar ·
│ │ MtmForm + inputs · Toast · Footer
│ ├── hooks/ useAuth (auth + profile/roles)
│ ├── service/ AuthStorage (token store + pub/sub),
│ │ ApiService (shared axios + endpoint wrappers)
│ └── style/ Tailwind + base CSS
├── docker-compose.yml MongoDB + Mongo Express (app service commented out)
└── README.md You are here.
- Java 17+ (Temurin / Adoptium recommended)
- Node.js 18+ and npm 9+
- Docker & Docker Compose (for MongoDB locally)
- Maven is bundled via
./mvnw; no global install needed
git clone git@github.com:vishu221b/MicroTimeManagement.git
cd MicroTimeManagementdocker compose up -dThis starts:
| Service | Port | What it is |
|---|---|---|
mtm_compose_mongo |
27017 |
MongoDB instance, no auth (local-dev only) |
m_express |
8081 |
Mongo Express UI at http://localhost:8081 |
The
mtm_testapp service indocker-compose.ymlis intentionally commented out — run the Spring app directly while developing so hot-reload + breakpoints work.
cd backend/api-service
./mvnw spring-boot:runThe API boots on http://localhost:8080/mtm-dev.
- Swagger UI: http://localhost:8080/mtm-dev/swagger-ui/index.html
- Health endpoint: http://localhost:8080/mtm-dev/actuator/health
In a separate terminal:
cd frontend
npm install
npm startThe dev server runs on http://localhost:3000 and talks to the backend at http://localhost:8080/mtm-dev/api/v1.
The dev profile (application-dev.yml) ships with sensible defaults so you can mvn spring-boot:run after docker compose up. The prod profile (application-prod.yml) requires every secret to come from the environment.
| Variable | Scope | Default | Purpose |
|---|---|---|---|
SPRING_PROFILES_ACTIVE |
both | dev |
Switches between dev and prod config |
MTM_JWT_SECRET |
dev (optional) / prod (required) | bundled dev fallback | Symmetric key for HS512 JWT signing |
MTM_MONGO_USERNAME |
prod | — | Mongo Atlas user |
MTM_MONGO_PASSWORD |
prod | — | Mongo Atlas password |
MONGO_STORE_CLUSTER |
prod | — | Atlas cluster hostname segment |
MONGO_STORE_DATABASE |
prod | — | Atlas database name |
LOG_FILE_PROPERTIES |
prod | — | Path for rolling log file |
The dev JWT secret has been left in the repo intentionally so the app boots out of the box. Override it in any environment you don't fully control.
A trimmed map of the v1 API — full schemas live in the Swagger UI.
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST |
/api/v1/auth/login |
public | Username/email + password → access + refresh tokens |
POST |
/api/v1/auth/refresh |
public | Refresh token → new access token |
POST |
/api/v1/auth/logout |
USER_OPS |
Revoke current session |
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST |
/api/v1/user/register |
public | Create new account (default roles: USER_OPS, ACTIVITY_CRUD) |
GET |
/api/v1/user/profile |
USER_OPS |
Current user, including roles |
PUT |
/api/v1/user/update |
USER_OPS |
Update own first/last name, email, username, DOB |
POST |
/api/v1/user/resetPassword |
USER_OPS |
Verify old password, set new password (≥ 8 chars) |
DELETE |
/api/v1/user/delete |
USER_OPS |
Soft-delete current account |
GET |
/api/v1/user/getByUserId |
ADMIN_OPS |
Look up any user by UID |
GET |
/api/v1/user/all |
ADMIN_OPS |
Paginated, sortable user listing |
POST |
/api/v1/user/addRole |
ADMIN_OPS |
Bulk add roles by uid/email/username |
DELETE |
/api/v1/user/removeRole |
ADMIN_OPS |
Bulk remove roles |
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST |
/api/v1/activity |
ACTIVITY_CRUD |
Create / append activity (overlap-validated) |
GET |
/api/v1/activity/getAllForDate?date= |
ACTIVITY_CRUD |
Activities for a date, scoped to current user |
PUT |
/api/v1/activity?date= |
ACTIVITY_CRUD |
Update an activity (metadata-only or full re-time) |
DELETE |
/api/v1/activity?date=&recordId= |
ACTIVITY_CRUD |
Remove a single activity from a day's record |
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST |
/api/v1/role |
ROLE_CRUD |
Create role |
GET |
/api/v1/role |
ROLE_CRUD |
List or get by id (paginated) |
PUT |
/api/v1/role |
ROLE_CRUD |
Rename role (conflict-checked) |
DELETE |
/api/v1/role?roleId= |
ROLE_CRUD |
Soft-delete role |
cd backend/api-service
./mvnw test # full suite
./mvnw test -Dtest=ActivityRecordServiceImplTest # one classService-layer coverage today (selected highlights):
| Suite | Notes |
|---|---|
ActivityRecordServiceImplTest |
14 cases — create / append / overlap / duplicate / invalid date / get / delete / update (metadata + retime + overlap rejection) |
UserServiceImplTest |
Registration, lookup, profile fetch, update (with cross-uid IDOR rejection), password change happy + wrong-old-password rejection |
RoleServiceImplTest |
8 cases — create / get / list / soft-delete / rename happy / no-op / conflict / not-found |
AuthenticationAndAuthorizationServiceImplTest |
Login happy + bad password |
ApiUtilsTest |
Pagination sanitisation edge cases |
A small number of
SessionServiceImplTestcases andApiServiceApplicationTests.contextLoadsare known-flaky (Mockito stubbing mismatch + Mongo dependency). They are tracked inCLAUDE.mdand excluded from the green-bar definition.
cd frontend
npm run build # production bundle (also runs ESLint)
npm test # Jest in watch mode (test coverage is the next milestone)- ✅ Auth flow with single-flight refresh, protected + admin routes
- ✅ Activity tracker CRUD UI
- ✅ Profile + password change UI
- ✅ Admin role management UI
- 📊 Dashboard with recent-activity summary
- 🧪 React Testing Library coverage for pages + hooks
- 👥 User → Role assignment UI in the admin panel
- 📆 Migrate string dates to
LocalDateand swap the DOB text input for a date picker - ⚡ Bulk role lookup (eliminate the per-request N+1 in
UserServiceImpl.replaceRoleIdsWithNamesForUser) - 🐳 Wire the prod Docker Compose app service with env-var-driven config
- In-app AI chat — a chat panel inside MTM that talks to an LLM (Anthropic at MVP, pluggable later) and uses task-shaped tools to act on the user's behalf — logging activities, summarising windows, looking up the day's entries. Auth is the user's normal MTM session, so there's no new token type, no new protocol surface. Single-turn-with-tools by design (no chatbot rabbit-hole): no persisted conversation memory across sessions, no general chit-chat fallback. Per-user daily token cap on an app key, with optional BYOK so power users can lift the cap with their own Anthropic key.
Architecture, premortem (the failure modes we're deliberately designing around), phasing, and the full task list live in CLAUDE.md under "AI Chat Integration (Planned, not started)".
A complete pending list lives in CLAUDE.md.
This is a personal portfolio project, but PRs and issues are welcome.
- Fork → branch (
feat/<thing>). - Backend changes ship with tests. Service-layer logic is TDD-friendly — write the test first.
- Run
./mvnw testandnpm run buildbefore pushing. - Open a PR against
mainwith a description of what changed and why.
The development conventions (package layout, exception hierarchy, response wrappers, callback contracts, etc.) are documented in CLAUDE.md and updated alongside every feature.
- Vishal Dogra — design, product direction, original build, review of every change.
- Claude (Anthropic) — co-author. Paired on the later milestones: auth hardening, the activity tracker UI, the profile + password flows, the admin role panel, and most of the service-layer test coverage.
If this project taught you something, or you spot a bug, please open an issue — feedback is the entire point of shipping it publicly.