A Spring Boot application for CBT-focused digital therapy workflows, including authentication, guided session chat, diary entries, progress tracking, and crisis support.
This project follows a layered architecture with clear responsibility boundaries:
- Controller layer (
controller/): HTTP API endpoints and request validation. - Service layer (
service/): Core business workflows (auth, sessions, diary, crisis, dashboard). - Persistence layer (
repository/+entity/): JPA entities and repositories for MySQL storage. - AI layer (
AiService,OpenAiService,service/rag/): OpenAI-backed and fallback logic for therapeutic responses, crisis detection, and CBT suggestions. - Cross-cutting layer (
config/,exception/,dto/): Security config, global error handling, and API contracts.
- A request enters a controller endpoint.
- Validation and input mapping happen in DTOs.
- Services orchestrate rules, state changes, and AI calls.
- Repositories persist and fetch entities from MySQL.
- Standardized success/error responses are returned.
Client -> Controller -> Service -> Repository -> MySQL
|\
| -> AI Service (OpenAI + RAG + fallback)
|
-> Domain/DTO mapping
- Spring Security protects all routes by default.
- Public auth routes:
/auth/register/auth/login/auth/refresh
- Session routes (
/sessions/**) requireROLE_PATIENTorROLE_DOCTOR. - Unauthorized and forbidden responses are normalized through JSON error envelopes.
- Authentication: register, login, refresh, logout, user listing, account delete.
- Sessions: CBT session library, session start/chat/end lifecycle.
- Diary: thought records, entries, insights, and cognitive distortion suggestions.
- Dashboard/Progress: trends, burnout recovery, and achievements.
- Crisis: crisis detection, coping strategies, and safety plan management.
- Java 17
- Spring Boot 4
- Spring MVC + Validation
- Spring Data JPA
- Spring Security
- MySQL
- springdoc OpenAPI (Swagger UI)
- JUnit + Mockito + Spring Test + JaCoCo
SE320/
├── README.md
└── SE320App/
├── pom.xml
├── docs/ERD.md
├── src/main/java/com/SE320/therapy/
│ ├── config/
│ ├── controller/
│ ├── dto/
│ ├── entity/
│ ├── exception/
│ ├── repository/
│ ├── service/
│ └── cli/commands/
└── src/main/resources/
├── application.properties
├── schema.sql
└── data.sql
- Java 17+
- Maven 3.9+
- MySQL 8+
export MYSQL_URL="jdbc:mysql://localhost:3306/digitaltherapy_db?createDatabaseIfNotExist=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC"
export MYSQL_USER=[username]
export MYSQL_PASSWORD=[password]
# Enables real OpenAI calls; hardcoded fallback exists for crisis management
export OPENAI_API_KEY="your_api_key"
# Optional: override default insecure dev secret
export JWT_SECRET="replace-with-a-long-random-secret"cd SE320App
mvn spring-boot:runThe legacy CLI remains available for local troubleshooting, but the web app is the default interface. To temporarily run the CLI:
cd SE320App
mvn spring-boot:run -Dspring-boot.run.arguments=--app.cli.enabled=trueThe primary UI is now the single-page frontend in SE320App/frontend. It runs on port 3000 and communicates with the Spring Boot API on port 8080 through REST endpoints.
cd SE320App/frontend
npm install
npm run devOpen http://localhost:3000.
The frontend includes "proxy": "http://localhost:8080" in package.json and Next.js rewrites for local development, so browser requests to /auth, /sessions, /diary, /progress, and /crisis are forwarded to the backend.
- Swagger UI:
http://localhost:8080/swagger-ui - OpenAPI JSON:
http://localhost:8080/v3/api-docs
POST /auth/registerPOST /auth/loginPOST /auth/refreshPOST /auth/logoutDELETE /auth/deleteGET /auth/users
GET /sessionsGET /sessions/{sessionId}POST /sessions/{sessionId}/startPOST /sessions/{sessionId}/chatPOST /sessions/{sessionId}/end
POST /diary/entries?userId={uuid}GET /diary/entries?userId={uuid}GET /diary/entries/{entryId}DELETE /diary/entries/{entryId}GET /diary/insights?userId={uuid}POST /diary/distortions/suggest
GET /crisis?userId={uuid}POST /crisis/crisis/detectGET /crisis/crisis/coping-strategiesGET /crisis/crisis/safety-plan?userId={uuid}PUT /crisis/crisis/safety-plan?userId={uuid}
GET /progress?userId={uuid}GET /progress/progress/monthly?userId={uuid}GET /progress/progress/weekly?userId={uuid}GET /progress/progress/burnout?userId={uuid}GET /progress/progress/achievements?userId={uuid}POST /progress/progress/achievements?userId={uuid}PUT /progress/progress/achievements/{achievementId}?userId={uuid}DELETE /progress/progress/achievements/{achievementId}?userId={uuid}
cd SE320App
mvn testGenerate coverage report:
cd SE320App
mvn verifyJaCoCo report is generated under SE320App/target/site/jacoco/.
The GitHub Actions pipeline uses a stage-gate architecture. Every stage must pass before downstream delivery or deployment work can run.
Developer push or PR
|
v
Stage 1: Build
|
+---- Stage 2: Unit Tests + JaCoCo 80% coverage gate
+---- Stage 3: Integration Tests
+---- Stage 4: Code Quality (Checkstyle, SpotBugs, ESLint)
+---- Stage 5: Dependency Check (OWASP Dependency-Check)
+---- Stage 6: Security Scan (Gitleaks)
|
v
Stage 7: Package Docker Images and Push to GHCR
|
v
Stage 8: Smoke Test Full Stack
|
v
Stage 9: Deploy to EC2 and Verify
.github/workflows/ci.yml: runs on pushes and pull requests tomainanddevelop. It builds the Java backend and Next.js frontend, then fans out into unit tests, integration tests, code quality, dependency checking, and secret scanning..github/workflows/cd-build.yml: runs after merges tomain. It builds backend and frontend Docker images, tags them with the commit SHA andlatest, pushes them to GitHub Container Registry, then starts the Docker Compose stack for smoke testing..github/workflows/cd-deploy.yml: runs after successful Continuous Delivery or manually throughworkflow_dispatch. It deploys the Docker Compose stack to the production EC2 instance over SSH and verifies the backend OpenAPI endpoint and frontend homepage.
- Build:
mvn -B -DskipTests package,npm ci, andnpm run build. - Unit tests and coverage:
mvn -B verifywith a JaCoCo instruction coverage minimum of 80%. - Integration tests: Spring Boot API and integration tests matching
*IntegrationTestand*ApiTest. - Code quality: Maven Checkstyle, SpotBugs, and frontend ESLint.
- Dependency check: OWASP Dependency-Check fails the build for CVSS 7+ vulnerabilities.
- Security scan: Gitleaks scans the repository history for committed secrets.
- Smoke test: Docker Compose starts the backend on port
8080and frontend on port3000; the workflow verifieshttp://localhost:8080/v3/api-docsandhttp://localhost:3000/.
Credentials are stored in GitHub Secrets and are never committed to the repository. Deployment uses the protected production environment so manual approvals or branch protections can be configured in GitHub.
Required deployment secrets:
EC2_HOST: EC2 public DNS name or Elastic IP.EC2_USER: SSH username for the EC2 instance.EC2_SSH_KEY: private SSH key with access to the instance.JWT_SECRET: production JWT signing secret.OPENAI_API_KEY: optional production OpenAI API key.
Docker images are published to GitHub Container Registry as:
ghcr.io/<owner>/<repository>-backend:<sha>ghcr.io/<owner>/<repository>-frontend:<sha>
The app includes a Spring AI MCP server that exposes the Digital Therapy Assistant service layer to MCP-compatible AI clients. The MCP layer keeps using the existing backend services and OpenAI/RAG implementation, so the AI behavior stays consistent with the REST API.
Build the jar first:
cd SE320App
mvn packageStart the MCP server over standard input/output:
java -jar target/SE320App-1.0-SNAPSHOT-exec.jar --app.cli.enabled=false --spring.ai.mcp.server.enabled=true --spring.ai.mcp.server.stdio=true --spring.main.web-application-type=none --spring.main.banner-mode=off --logging.level.root=OFF--app.cli.enabled=false is required because the old CLI also uses stdin/stdout. In MCP stdio mode, the AI client owns those streams and exchanges JSON-RPC messages with the Spring Boot process.
A Claude Desktop-compatible MCP config is provided at:
SE320App/docs/claude_desktop_mcp_config.json
The server is not Claude-specific. Claude Desktop is just a convenient MCP host for testing; the tool implementations still use the existing OpenAI-backed AiService.
start_session(userId, sessionId)chat_in_session(sessionId, message)end_session(sessionId, reason)get_session_library(userId)get_session_history(userId)create_diary_entry(userId, situation, automaticThought, emotions)analyze_thought(thought)suggest_reframing(thought, distortionIds)detect_crisis(text)get_weekly_progress(userId)get_insights(userId)get_coping_strategies()
For chat_in_session and end_session, use the userSessionId returned by start_session as the active sessionId.
therapy://sessions/{sessionId}therapy://diary/{userId}therapy://diary/entry/{entryId}therapy://progress/{userId}therapy://distortionstherapy://crisis/resourcestherapy://safety-plan/{userId}
thought_analysis(thought)session_summary(sessionId)weekly_check_in(userId)
The ERD is documented in:
SE320App/docs/ERD.md
schema.sqlanddata.sqlseed core CBT and coping data on startup.- If
OPENAI_API_KEYis not set, AI features still work using fallback logic. - Authentication uses JWT access and refresh tokens signed with
jwt.secret.