Personal and family medical-records app with patient and doctor experiences.
Patients organize folders and files (labs, imaging, prescriptions), track doctors, appointments, and medications, manage family members, and share a view-only health summary with a clinician via a short-lived code/QR. Doctors redeem that code and browse linked patients without write access.
| Piece | Location |
|---|---|
| Backend API | Repo root (com.example.medhistroy) |
| Frontend SPA | MedHistory/ |
| Compose stack | docker-compose.yml |
Artifact / folder name is
medhistroy(historical spelling). Product name is MedHistory.
Patient UI previews. Links open the full-size PNGs (faster than embedding huge blobs inline).
| Screen | Full size |
|---|---|
| Dashboard | 1.png |
| Records | 2.png |
| Profile & settings | 3.png |
| Timeline | 4.png |
Home overview: medical summary card, folder/file/doctor/visit counts, today’s medicines, recent uploads, and pinned folders.
Folder grid with type filters (lab, prescription, dental, …), pin status, doctor, and file counts.
Profile, health fields, push reminders, language (8 locales), and light/dark/system theme.
Chronological visits with notes, prescriptions, linked folders, follow-ups, and specialty filters.
| Layer | Stack |
|---|---|
| Backend | Java 21, Spring Boot 4.1, Web MVC, Security (stateless), Data JPA, Maven |
| Auth | Custom HS256 JWT (JwtService), BCrypt passwords |
| Database | MySQL 8.4 (runtime); H2 (tests) |
| Files | Disk under uploads/ (DB stores relative path only) |
| Frontend | Vue 3, Vite, Pinia, Vue Router, Vue I18n, Tailwind CSS 4 |
| Infra | Docker multi-stage builds; nginx serves SPA and proxies /api |
Browser
├─ Local: Vite → http://localhost:8081/api
└─ Docker: :8090 nginx (SPA) + /api → backend:8081
│
▼
Spring Boot :8081
│
JwtAuthFilter (Bearer or ?token=)
│
Controllers + AccessService
│
MySQL ◄── JPA entities / seed JSON
Disk ◄── FileStorageService (/app/uploads)
- JSON is snake_case end-to-end (
spring.jackson.property-naming-strategy=SNAKE_CASE). - Frontend client:
MedHistory/src/api/index.js→Authorization: Bearer <jwt>. <img>/<iframe>previews append?token=because they cannot set headers.- On
401, the client clears session and routes to login.
Authorization is app-level (AccessService), not Spring roles:
| Capability | Who |
|---|---|
| Write | Self + family members you own |
| Read | Write set + patients with an ACTIVE doctor link |
| Doctor links | View-only — never grant write |
Two different “doctor” concepts:
Doctorentity — patient’s care-provider contact (address book).User.role = doctor— clinician account that redeems share codes.
| Feature | Backend | Frontend |
|---|---|---|
| Login / register | AuthController (/api/auth/*), BCrypt, roles patient | doctor |
LoginView, RegisterView, medStore.login/register |
| JWT session | JwtService + JwtAuthFilter; secret JWT_SECRET / app.jwt.secret |
localStorage: med_token, med_user_id |
| Forgot password | — | ForgotPasswordView (UI stub only) |
| Feature | Backend | Frontend |
|---|---|---|
| Dashboard | Aggregates via existing list APIs | DashboardView |
| Member switcher | Scope by userId / access checks |
MemberSwitcher; setActiveMember refreshes caches |
| Family members | FamilyController — create managed User + FamilyRelation |
FamilyView, AddMemberSheet |
| Profile + photo | UserController CRUD + photo upload/stream |
ProfileView, EditProfileSheet |
| Health summary | User fields (blood, allergies, conditions, meds) | SummaryView, SummaryCard |
| Share with doctor | DoctorLinkController — 8-char code, ~15 min TTL, PENDING→ACTIVE/EXPIRED/REVOKED |
ShareWithDoctorSheet (QR), doctor redeems via RedeemCodeSheet |
| Doctor contacts | DoctorController |
DoctorsView, DoctorDetailView, AddDoctorSheet |
| Record folders | FolderController (type / doctor filters; cascade file delete) |
RecordsView, FolderDetailView, CreateFolderSheet |
| File upload / preview | FileController multipart + /content stream; max 25MB file / 30MB request |
UploadSheet, FilePreviewSheet (XHR progress) |
| Appointments / timeline | AppointmentController |
TimelineView, LogAppointmentSheet |
| Medications | MedicationController; dose times in medication_times |
MedicinesView, AddMedicineSheet |
| Dose “taken” / notifications | — (client-derived) | NotificationsView; dismissals in localStorage |
| Search | SearchController — LIKE over doctors / folders / files |
SearchView → medStore.search |
| i18n + theme | — | settings store; locales en, bn, ar, fr, es, hi, zh, pt; light/dark/system |
| Feature | Backend | Frontend |
|---|---|---|
| Doctor home | GET /api/doctor-links/patients |
DoctorHomeView |
| Redeem share code | POST /api/doctor-links/redeem |
RedeemCodeSheet |
| View patient records | Same read APIs under viewable access | Records / summary / etc. with active patient context |
DataSeeder loads src/main/resources/seed/*.json when the users table is empty.
- Demo password for all seeded users:
demo1234 - Example patient:
atiqur.itc@gmail.com - Example doctor: see
seed/users.json/seed/doctor_patient_links.json(pre-linked ACTIVE where configured)
| Entity | Table | Role |
|---|---|---|
User |
users |
Account + profile; role; blood/allergies; emergency contact; conditions/meds lists |
FamilyRelation |
family_relations |
Owner ↔ managed member |
Doctor |
doctors |
Contact doctor for a member |
Folder |
folders |
Record folder (type, dates, pin, file count) |
MedFile |
files |
File metadata; storagePath on disk |
Appointment |
appointments |
Visits, notes, follow-up, optional linked folder |
Medication |
medications |
Schedule + dosage |
DoctorPatientLink |
doctor_patient_links |
Share codes / view grants |
Most extend Auditable (created_at / updated_at).
Base URL: http://localhost:8081 (or same-origin /api behind nginx).
POST /api/auth/loginPOST /api/auth/registerGET /api/testGET /— API status map
| Prefix | Notes |
|---|---|
/api/users |
List viewable users; CRUD; photo upload/get/delete |
/api/family |
List / create relation / create member / delete |
/api/doctors |
CRUD; list by userId |
/api/folders |
CRUD; filter userId, type, doctorId |
/api/files |
POST /upload, GET /{id}/content, list by folder/user, CRUD |
/api/appointments |
CRUD; list by userId or doctorId |
/api/medications |
CRUD; list by userId |
/api/search |
q, userId, optional scope (all | doctors | folders | files) |
/api/doctor-links |
Share code, redeem, list links/patients, revoke |
CORS allows localhost / 127.0.0.1 with credentials for local Vite.
medhistroy/
├── docker-compose.yml # mysql + backend + frontend
├── Dockerfile # Spring Boot image
├── .env.example
├── pom.xml
├── src/main/java/... # API, security, services, models
├── src/main/resources/
│ ├── application.properties
│ └── seed/ # Demo JSON
├── uploads/ # Local file storage (gitignored)
└── MedHistory/ # Vue SPA
├── Dockerfile
├── nginx.conf # SPA + /api reverse proxy
├── package.json
└── src/
├── api/ # HTTP client
├── stores/ # Pinia (medStore, settings)
├── views/ # Pages
├── components/
├── router/
└── i18n/
Prereqs: Docker Desktop (daemon running).
cd /path/to/medhistroy
cp .env.example .env # optional; edit secrets
docker compose up --build| Service | Host URL / port |
|---|---|
| Frontend (UI) | http://localhost:8090 |
| Backend API | http://localhost:8081 |
| MySQL | localhost:3310 → container 3306 |
Stop:
docker compose downWipe DB + uploads volumes:
docker compose down -vCompose wires:
- Backend waits until MySQL is healthy.
- Frontend build bakes
VITE_API_URL=/api; nginx proxies/api/→backend:8081. - Uploads persist in volume
backend_uploads→/app/uploads.
- Java 21
- MySQL 8.x with database
medhistory(or rely oncreateDatabaseIfNotExist) - Node.js compatible with
MedHistory/package.json(Vite 8 / Node 22+ recommended)
Default application.properties expects:
- URL:
jdbc:mysql://localhost:3306/medhistory?... - User:
root - Password: empty
./mvnw spring-boot:run
# API → http://localhost:8081Override as needed:
export SPRING_DATASOURCE_URL='jdbc:mysql://localhost:3310/medhistory?createDatabaseIfNotExist=true&serverTimezone=UTC&allowPublicKeyRetrieval=true&useSSL=false'
export SPRING_DATASOURCE_PASSWORD=medhistory
export JWT_SECRET='your-long-secret'
./mvnw spring-boot:runTests (H2, no MySQL):
./mvnw testcd MedHistory
npm install
npm run devAPI base defaults to http://localhost:8081/api. Override:
VITE_API_URL=http://localhost:8081/api npm run devUseful scripts: npm run build, npm run preview, npm run test:unit, npm run lint.
| Variable | Used by | Purpose |
|---|---|---|
MYSQL_ROOT_PASSWORD |
Compose MySQL + backend | Root / datasource password (default medhistory) |
JWT_SECRET |
Backend | JWT HMAC secret (override in production) |
SPRING_DATASOURCE_URL |
Backend | JDBC URL |
SPRING_DATASOURCE_USERNAME |
Backend | DB user |
SPRING_DATASOURCE_PASSWORD |
Backend | DB password |
VITE_API_URL |
Frontend build | API base (/api in Docker; full URL in local Vite) |
Also in application.properties:
server.port=8081app.upload-dir=uploadsapp.jwt.expiration-ms(default 24h)- Multipart limits 25MB / 30MB
Copy .env.example → .env next to docker-compose.yml for Compose. Vite does not read that file for npm run dev.
After first boot with an empty DB (seed runs automatically):
| Role | Password | |
|---|---|---|
| Patient | atiqur.itc@gmail.com |
demo1234 |
| Doctor | dr.farhana@example.com |
demo1234 |
| Other seeded users | src/main/resources/seed/users.json |
demo1234 |
- Forgot-password is frontend-only; no reset API yet.
- Medication “taken today” and notification dismissals are client-side only.
- Family members created via
/api/family/membersare managed profiles (may not be full login accounts). - Change
JWT_SECRETand MySQL password before any shared/production deploy.



