Role-based dormitory intake, onboarding, submission review, and room selection platform.
This repository contains a working full-stack implementation of a student dormitory move-in workflow:
- super admins configure admission cycles, dormitories, room inventory, employees, and document requirements
- accepted students are imported from CSV and receive onboarding emails
- students activate accounts, upload profile photos, upload documents and fee proofs, and submit their package
- employees review every uploaded item, request corrections, approve, or reject submissions
- students can self-select and temporarily hold beds when the dormitory allows direct room selection
Current state: this is a strong local/demo environment for the end-to-end workflow. It is not yet production-hardened.
- JWT-based authentication with refresh cookie support
- First-time student account activation from onboarding email links
- Role-specific React dashboards for
SUPER_ADMIN,EMPLOYEE, andSTUDENT - Admission cycle, dormitory, block, room, and document requirement management
- CSV-based acceptance import with duplicate handling and onboarding email delivery
- Employee account management
- Student profile photo upload with image validation
- Student document upload, fee proof upload, exemption support upload, and final package submission
- Employee queue, item-by-item review, final decisioning, and audit history
- Self-service bed hold and release workflow for enabled dormitories
- Docker Compose stack with PostgreSQL, backend, frontend, and Mailpit
- Flyway-backed database schema migrations and backend integration tests
flowchart LR
A[Super Admin Setup] --> B[Import Accepted Students CSV]
B --> C[Onboarding Email Sent]
C --> D[Student Activates Account]
D --> E[Student Uploads Photo, Documents, Fees]
E --> F[Student Holds Bed if Self-Selection Is Enabled]
F --> G[Student Submits Package]
G --> H[Employee Reviews Each Item]
H --> I{Decision}
I -->|Correction Required| E
I -->|Approved| J[Approved for Move-In]
I -->|Rejected| K[Rejected Package]
flowchart TB
Browser[Browser]
Frontend[React 19 + Vite + Bootstrap]
Nginx[Nginx SPA Container]
Backend[Spring Boot API]
Db[(PostgreSQL)]
Mail[Mailpit or SMTP]
Browser --> Frontend
Frontend -->|Local dev| Backend
Browser -->|Docker| Nginx
Nginx -->|/api proxy| Backend
Backend --> Db
Backend --> Mail
| Layer | Current choice |
|---|---|
| Backend | Java 21, Spring Boot 4.0.3, Spring Security, Spring Web, Spring Data JPA |
| Database | PostgreSQL, Flyway migrations |
| Authentication | JWT access token + HTTP-only refresh cookie |
| Frontend | React 19.1, React Router 7.4, Bootstrap 5.3, Vite 7.1 |
| Local infrastructure | Docker Compose, Mailpit, Nginx |
| Testing | JUnit integration tests on the backend, frontend production build validation |
.
├── src/main/java/ Spring Boot application code
├── src/main/resources/ application properties + Flyway migrations
├── src/test/java/ backend integration tests
├── frontend/ React/Vite application
├── docker/backend-entrypoint.sh backend container bootstrap
├── compose.yaml full local stack
├── compose.mailpit.yaml Mailpit-only helper stack
├── acceptance-import-example.csv sample student import file
├── .env.example backend + compose environment template
└── frontend/.env.example frontend environment template
| Role | Current UI/API surface | What the role can do |
|---|---|---|
SUPER_ADMIN |
/super-admin, /api/admin/setup/**, /api/admin/employees/**, /api/admin/acceptance-imports/** |
manage cycles, dormitories, blocks, rooms, document requirements, employees, onboarding accounts, and CSV imports |
EMPLOYEE |
/employee, /api/employee/submissions/** |
search queue, inspect submission detail, download files, review documents and fee items, request correction, approve, reject |
STUDENT |
/student, /api/student/** |
see dormitory offer, upload profile photo, upload documents and fees, download requirement templates, hold/release bed, submit or resubmit package |
| Shared | /guide, /process-flow, /api/auth/** |
sign in, activate account, review role instructions, follow the end-to-end process map |
This is the best way to run the current project exactly as it is wired today.
docker compose up --build| Service | URL | Notes |
|---|---|---|
| Frontend | http://localhost:5173 | Nginx-served SPA |
| Backend API | http://localhost:8080 | Spring Boot API |
| Mailpit UI | http://localhost:8025 | inspect onboarding emails |
| Mailpit SMTP | localhost:1025 |
backend mail target in local/dev |
| PostgreSQL | localhost:5432 |
database exposed to host |
- if
JWT_SECRETis not provided, the backend container generates an ephemeral development secret on startup - the frontend container uses same-origin
/apicalls and proxies them to the backend through Nginx - Mailpit is enabled by default in the full stack
Stop the stack:
docker compose downStop the stack and remove the Postgres volume:
docker compose down -v| Tool | Version expected by the repo |
|---|---|
| Java | 21 |
| Node.js | 22+ |
| npm | current Node-compatible npm |
| PostgreSQL | 16-compatible |
Create a JWT secret:
openssl rand -base64 32Copy the template:
cp .env.example .envImportant values:
JWT_SECRET=<base64 secret with at least 32 decoded bytes>
SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/dormitory_db?currentSchema=public
SPRING_DATASOURCE_USERNAME=<your database username>
SPRING_DATASOURCE_PASSWORD=<your database password>
APP_FRONTEND_BASE_URL=http://localhost:5173
APP_SECURITY_ALLOWED_ORIGINS=http://localhost:5173,http://127.0.0.1:5173The backend automatically imports .env because application.properties contains:
spring.config.import=optional:file:.env[.properties]Create the database if needed:
createdb dormitory_db./mvnw spring-boot:runThe API will be available at http://localhost:8080.
cp frontend/.env.example frontend/.env.local
cd frontend
npm ci
npm run devBy default the frontend points to:
VITE_API_BASE_URL=http://localhost:8080The frontend dev server will be available at http://localhost:5173.
Run Mailpit only:
docker compose -f compose.mailpit.yaml up -dThen run the backend with the Mailpit profile:
SPRING_PROFILES_ACTIVE=mailpit ./mvnw spring-boot:runOr write emails to disk instead of SMTP:
SPRING_PROFILES_ACTIVE=mailpreview ./mvnw spring-boot:runMail previews are written to target/mail-preview by default.
The full template lives in .env.example. The most important variables are grouped below.
| Variable | Default | Purpose |
|---|---|---|
JWT_SECRET |
none | required outside Docker; must be Base64 and decode to at least 32 bytes |
JWT_REFRESH_COOKIE_SECURE |
false |
marks the refresh cookie as Secure |
SPRING_DATASOURCE_URL |
jdbc:postgresql://localhost:5432/dormitory_db?currentSchema=public |
JDBC URL |
SPRING_DATASOURCE_USERNAME |
empty | database username |
SPRING_DATASOURCE_PASSWORD |
empty | database password |
SPRING_JPA_SHOW_SQL |
true |
SQL logging |
SPRING_JPA_HIBERNATE_DDL_AUTO |
update |
Hibernate schema mode |
APP_FRONTEND_BASE_URL |
http://localhost:5173 |
base URL used in onboarding emails |
APP_SECURITY_ALLOWED_ORIGINS |
http://localhost:5173,http://127.0.0.1:5173 |
CORS allow-list |
| Variable | Default | Purpose |
|---|---|---|
APP_SEED_ENABLED |
false |
forces a destructive re-seed of the focused demo dataset |
APP_BOOTSTRAP_SUPERADMIN_USERNAME |
empty | optional local bootstrap admin username |
APP_BOOTSTRAP_SUPERADMIN_PASSWORD |
empty | optional local bootstrap admin password |
APP_BOOTSTRAP_SUPERADMIN_FULL_NAME |
Local Super Admin |
display name for the bootstrap admin |
Important current behavior:
- if the database is completely empty, the focused demo dataset is seeded automatically once
APP_SEED_ENABLED=truewipes and recreates that focused dataset- the
APP_BOOTSTRAP_SUPERADMIN_*variables are only used when startup skips the empty-database seed path and there are still no users
| Variable | Default | Purpose |
|---|---|---|
APP_MAIL_FROM |
no-reply@student-dormitory.local |
sender address |
APP_MAIL_PREVIEW_ENABLED |
false |
writes emails to disk instead of SMTP |
APP_MAIL_PREVIEW_DIRECTORY |
target/mail-preview |
preview output directory |
APP_MAIL_LOCAL_DETECTION_HOSTS |
127.0.0.1,localhost,host.docker.internal,mailpit |
hosts checked for local Mailpit |
SPRING_MAIL_HOST |
empty | explicit SMTP host |
SPRING_MAIL_PORT |
1025 |
SMTP port |
SPRING_MAIL_USERNAME |
empty | SMTP auth username |
SPRING_MAIL_PASSWORD |
empty | SMTP auth password |
SPRING_MAIL_SMTP_AUTH |
false |
SMTP auth toggle |
SPRING_MAIL_SMTP_STARTTLS |
false |
STARTTLS toggle |
SPRING_MAIL_SMTP_STARTTLS_REQUIRED |
false |
require STARTTLS |
DOCKER_SPRING_MAIL_HOST |
mailpit |
backend container SMTP host |
DOCKER_SPRING_MAIL_PORT |
1025 |
backend container SMTP port |
Mail resolution order in the current backend:
- explicit SMTP via
SPRING_MAIL_HOST - auto-detected local Mailpit host
- filesystem preview when preview mode is enabled
| Variable | Default | Purpose |
|---|---|---|
VITE_API_BASE_URL |
http://localhost:8080 in local dev |
backend base URL for the Vite app |
Warning:
APP_SEED_ENABLED=truedeletes existing application data and recreates the focused demo environment.
When the database is empty, the application seeds a focused dataset for the 2026/2027 move-in cycle.
| Area | Current seeded value |
|---|---|
| Admission cycle | 2026/2027 Вселување во студентски домови |
| Dormitory | Гоце Делчев in Скопје |
| Blocks | А, Б, В, Г |
| Floors per block | 15 |
| Rooms per floor | 24 |
| Beds per room | 2 |
| Total beds | 2880 |
| Employees | employee, reviewer1, reviewer2, reviewer3, reviewer4 |
| Super admin | superadmin |
| Students | student001 through student100 |
| Bed hold duration | 20 minutes |
| Roommate group size | 2 |
Document requirements:
MOVE_IN_PACKAGEwith a downloadable text templateBIRTH_CERTIFICATE_COPYCURRENT_SEMESTER_VERIFICATIONINDEX_COPYPHOTOS
Fee items:
DOCUMENT_PACKAGE_FEE-500.00 MKDACCOMMODATION_FEE-3490.00 MKD, supports exemption evidenceMAINTENANCE_FEE-1200.00 MKD
The demo students are intentionally spread across workflow states:
| Status | Count |
|---|---|
DRAFT |
45 |
SUBMITTED |
27 |
CORRECTION_REQUIRED |
12 |
APPROVED |
11 |
REJECTED |
5 |
| Username | Password | Role |
|---|---|---|
superadmin |
superadmin123 |
Super Admin |
employee |
employee123 |
Employee |
reviewer1 to reviewer4 |
employee123 |
Employee |
student001 to student100 |
student123 |
Student |
Use acceptance-import-example.csv as the source of truth for the current import contract.
Expected header:
username,fullName,email,personalIdentifier,indexNumber,cycleName,dormitoryName,acceptancePreference,submissionDeadline| Column | Meaning |
|---|---|
username |
student account username |
fullName |
display name stored on the student profile and offer |
email |
onboarding destination and student profile email |
personalIdentifier |
student identifier |
indexNumber |
student academic index number |
cycleName |
must match an existing admission cycle name |
dormitoryName |
must match an existing dormitory name |
acceptancePreference |
PRIMARY or ALTERNATIVE |
submissionDeadline |
ISO date, for example 2026-11-15 |
- empty files are rejected
- duplicate rows inside the uploaded CSV are reported
- duplicate cycle + user offers already stored in the database are reported
- new student usernames create inactive student accounts with activation tokens
- existing active student accounts can be linked to new acceptance offers
- onboarding email delivery status is stored per imported offer
This is not a Swagger-generated list. It is the practical API surface that is clearly implemented today.
| Route group | Purpose |
|---|---|
POST /api/auth/login |
authenticate and return access token |
POST /api/auth/refresh |
rotate the access session using refresh cookie |
POST /api/auth/logout |
revoke refresh token and clear cookie |
GET /api/auth/me |
return the current authenticated user |
POST /api/auth/activate |
activate first-time student account |
GET/POST/PUT/DELETE /api/admin/setup/... |
manage cycles, dormitories, blocks, rooms, and document requirements |
GET/POST/PUT/DELETE /api/admin/employees... |
manage employee accounts |
POST /api/admin/acceptance-imports |
import accepted students CSV |
GET /api/admin/acceptance-imports/accounts |
onboarding account overview |
POST /api/admin/acceptance-imports/accounts/{id}/resend-email |
resend onboarding mail |
GET /api/employee/submissions/queue |
employee queue and filtering |
GET /api/employee/submissions/{id} |
detailed review workspace |
POST /api/employee/submissions/{id}/documents/{requirementId}/review |
review one document |
POST /api/employee/submissions/{id}/fees/{feeItemId}/review |
review one fee item |
POST /api/employee/submissions/{id}/request-correction |
request correction |
POST /api/employee/submissions/{id}/approve |
approve submission |
POST /api/employee/submissions/{id}/reject |
reject submission |
GET /api/student/acceptance-offer |
student dormitory offer |
GET /api/student/profile |
student profile summary |
POST /api/student/profile/photo |
upload profile photo |
GET /api/student/profile/photo |
fetch profile photo |
GET /api/student/submission |
submission workspace |
POST /api/student/submission/documents/{requirementId}/upload |
upload one document |
POST /api/student/submission/fees/{feeItemId}/upload |
upload payment proof or exemption support |
POST /api/student/submission/submit |
submit or resubmit the package |
GET /api/student/submission/documents/{requirementId}/template |
download requirement template |
GET /api/student/room-selection |
room and bed availability |
POST /api/student/room-selection/beds/{bedId}/hold |
hold a bed |
DELETE /api/student/room-selection/current |
release the current hold |
GET /api/faculty/... |
separate smaller public faculty endpoint still present in the backend |
- access token lifetime:
900seconds - refresh token lifetime:
2592000seconds - refresh token is stored in an HTTP-only cookie
- the frontend stores the access token in browser local storage and refreshes it automatically
| Upload type | Current rule |
|---|---|
| Student profile photo | max 2 MB, must be a valid PNG, JPEG, or GIF image |
| Student documents | max 5 MB per file |
| Student fee proof / exemption file | max 5 MB per file |
Uploaded files are stored in the database in the current implementation.
Flyway migrations currently cover:
| Migration | Focus |
|---|---|
V1 |
auth foundation |
V2 |
master data foundation |
V3 |
employee scope and permissions |
V4 |
acceptance offers |
V5 |
submission workflow |
V6 |
review workflow |
V7 |
bed selection workflow |
V8 |
student profile photo |
V9 |
student email and staff member updates |
V10 |
acceptance-offer onboarding fields |
Current default persistence notes:
- Hibernate is configured with
ddl-auto=update - Flyway runs against the
publicschema - PostgreSQL is the only supported database in the current project configuration
./mvnw testThese are integration tests, not an in-memory test suite. The current test setup expects a reachable PostgreSQL database. If your local PostgreSQL roles differ from the repo defaults, override the datasource settings before running the suite or point the tests at the Compose database.
Current backend tests cover:
- auth controller
- CORS
- admin employee management
- admin setup
- acceptance import
- student profile
- student submission workflow
- student room selection workflow
- employee review workflow
cd frontend
npm ci
npm run buildCurrent reality:
- there is no dedicated frontend automated test suite in
package.jsonyet - the frontend verification path today is a production build
- The frontend stores access tokens in
localStorage; that should be reviewed before any public deployment. - File uploads and profile photos are stored directly in PostgreSQL rather than external object storage.
SPRING_JPA_HIBERNATE_DDL_AUTO=updateis convenient for local development but usually not what you want in production.- The app auto-seeds the focused demo dataset whenever it starts against a completely empty database.
- Docker Compose settings are tuned for local development, not for hardened deployment.
- The login page still includes a legacy quick-fill student demo account, while the current focused seed creates
student001throughstudent100. - Employee permission profiles currently only expose
STAFF_MEMBER. - Backend tests currently depend on a real PostgreSQL connection instead of an embedded test database.
- There is no generated OpenAPI or Swagger document in the current repo.
# Start the full local stack
docker compose up --build
# Start only Mailpit
docker compose -f compose.mailpit.yaml up -d
# Run the backend locally
./mvnw spring-boot:run
# Run backend tests
./mvnw test
# Package the backend jar
./mvnw -DskipTests package
# Run the frontend locally
cd frontend
npm ci
npm run dev
# Validate the frontend production build
cd frontend
npm ci
npm run buildEMAIL_SETUP.mdDORMITORY_SYSTEM_REQUIREMENTS.mdIMPLEMENTATION_PLAN.mdfrontend/README.md
Built for a full dormitory intake workflow: setup, onboarding, submission, review, and room assignment.
