Study Flow Sync is a full-stack student planner built with Java 17, Spring Boot, and a static HTML/CSS/JavaScript frontend. It keeps the original StudyFlow course, task, and grade dashboard, then adds a Canvas-powered smart study planner that syncs Canvas courses and assignments into a local SQLite database.
- User registration and login APIs
- BCrypt password hashing
- JWT authentication for API access
- Protected course, task, grade, and dashboard endpoints
- Per-user data isolation (users only access their own records)
- Course management with create, read, update, and delete support
- Task management with status tracking (
TODO,IN_PROGRESS,DONE) - Task search/filter/sort by title, status, priority, course, and sort mode
- Grade management with weighted score calculations
- Dashboard summary statistics (courses, tasks, completion, overdue)
- Canvas connection settings screen with base URL, access token input, and Test Connection
- Canvas sync for courses, assignments, To-do items, and Planner items when supported
- Local SQLite storage for Canvas
courses,assignments,tasks, andsync_logs - Smarter Canvas task buckets: Today, This Week, Overdue, No Due Date, and High Priority
- Priority score from 0 to 100 based on due date, overdue/completed state, and missing status
- Missing assignment detector that shows assignments even when Canvas To-do omits them
- Source labels for Assignment API, Todo API, and Planner API
- Mock Canvas sync data for demos when Canvas is unavailable
- Frontend login/register flow and authenticated dashboard UI
- H2 in-memory database for local development
- JUnit + Mockito unit tests and integration tests for auth/security
- GitHub Actions CI (
mvn test)
- Java 17
- Spring Boot 3.3.5
- Spring Web
- Spring Data JPA
- Spring Security
- Java 11+
HttpClient - Jackson JSON parsing
- SQLite + JDBC (
sqlite-jdbc) - JWT (
jjwt) - Bean Validation
- H2 Database
- Maven
- JUnit 5
- Mockito
- HTML
- CSS
- JavaScript
- Java 17 or newer
- Maven 3.9 or newer
- Git
git clone <your-repository-url>
cd StudyFlowmvn spring-boot:runApplication URL:
http://localhost:8080
If port 8080 is already in use, stop the other local app first and rerun the same command. For a one-off alternate port during local troubleshooting, you can use:
mvn spring-boot:run -Dspring-boot.run.arguments=--server.port=8081StudyFlow uses stateless JWT authentication.
For local development, StudyFlow generates an in-memory JWT signing key if STUDYFLOW_JWT_SECRET is not set. If you want existing JWTs to stay valid across app restarts, set a local environment variable before starting the app:
export STUDYFLOW_JWT_SECRET="$(openssl rand -base64 32)"
mvn spring-boot:runDo not commit real JWT secrets, Canvas tokens, .env files, local config files, or local database files.
When the app starts with an empty database, one demo account is created:
name: Demo Student
email: student@example.com
password: password123
curl -X POST http://localhost:8080/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"name": "Alice Student",
"email": "alice@example.com",
"password": "password123"
}'curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "alice@example.com",
"password": "password123"
}'Example auth response:
{
"token": "<jwt-token>",
"tokenType": "Bearer",
"userId": 2,
"name": "Alice Student",
"email": "alice@example.com"
}Use the token for protected endpoints:
curl http://localhost:8080/api/dashboard/summary \
-H "Authorization: Bearer <jwt-token>"The H2 console is available at:
http://localhost:8080/h2-console
Use these settings:
JDBC URL: jdbc:h2:mem:studyflow
Username: sa
Password:
Open:
http://localhost:8080/
Flow:
- Register a new account (or sign in with the seeded demo account).
- The app stores the JWT in browser storage.
- All dashboard operations call protected APIs with
Authorization: Bearer <token>. - Use the top navigation tabs for Courses, Tasks, Canvas Sync, and Grades.
- Open the Canvas Sync tab to test a Canvas connection, sync real Canvas data, or load mock demo data.
- Start the app with
mvn spring-boot:run. - Open
http://localhost:8080/. - Sign in or register.
- Click the
Canvas Synctab in the authenticated dashboard header.
The Canvas Sync page contains the Canvas base URL field, access token field, Test connection button, Sync Canvas button, Load demo Canvas data button, task bucket filters, and recent local sync logs.
- Log in to your Canvas instance, for example
https://school.instructure.com. - Open Account, then Settings.
- Under Approved Integrations, choose New Access Token.
- Add a purpose such as
Study Flow Sync. - Choose an expiration date if your school requires one.
- Generate the token and copy it immediately.
If your school disables personal access tokens, ask your Canvas administrator whether API tokens are available for students.
In the Canvas Sync tab:
- Enter the Canvas base URL, for example
https://school.instructure.com. - Paste the access token.
- Click Test connection.
- Click Sync Canvas.
The token is not saved by the backend. The browser keeps it only in sessionStorage for the current browser session.
Use mock sync when you want to demo the Canvas dashboard before getting a real token:
- Start the app and sign in.
- Open the
Canvas Synctab. - Leave the Canvas base URL and access token blank.
- Click
Load demo Canvas data. - Confirm the task list shows demo Canvas assignments, including High Priority, Overdue, and No Due Date examples.
Mock sync uses built-in sample data only. It does not call Canvas.
- Start the app and sign in.
- Open the
Canvas Synctab. - Enter your Canvas base URL, such as
https://school.instructure.com. - Paste your personal access token into the Access token field.
- Click
Test connectionand confirm the success message reports active courses. - Click
Sync Canvas. - Review the Canvas task buckets:
Today,This Week,Overdue,No Due Date, andHigh Priority. - Check source badges on tasks. Assignments found through the Assignments API still appear even if Canvas To-do did not list them.
You can avoid typing the Canvas settings into the UI by setting local environment variables before starting the app:
export STUDYFLOW_CANVAS_BASE_URL="https://school.instructure.com"
export STUDYFLOW_CANVAS_TOKEN="<canvas-token>"
mvn spring-boot:run- Canvas data is stored locally in
data/canvas-sync.db. data/,.envfiles, local SQLite/database files, log files, and local Canvas config file names are ignored by Git.- The Canvas token is never hardcoded, stored in SQLite, or printed in application logs.
- The backend supports Canvas pagination through
Linkheaders. - Invalid URLs, invalid tokens, network failures, rate limits, empty courses, and assignments with no due date are handled with safe API errors or warnings.
All endpoints below (except /api/auth/**) require Authorization: Bearer <jwt-token>.
curl http://localhost:8080/api/dashboard/summary \
-H "Authorization: Bearer <jwt-token>"Create a course:
curl -X POST http://localhost:8080/api/courses \
-H "Authorization: Bearer <jwt-token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Algorithms",
"description": "Graph algorithms and dynamic programming"
}'Get current user's courses:
curl http://localhost:8080/api/courses \
-H "Authorization: Bearer <jwt-token>"Get one course:
curl http://localhost:8080/api/courses/1 \
-H "Authorization: Bearer <jwt-token>"Update a course:
curl -X PUT http://localhost:8080/api/courses/1 \
-H "Authorization: Bearer <jwt-token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Advanced Algorithms",
"description": "Greedy, graphs, and DP"
}'Delete a course:
curl -X DELETE http://localhost:8080/api/courses/1 \
-H "Authorization: Bearer <jwt-token>"Create a task for a course:
curl -X POST http://localhost:8080/api/courses/1/tasks \
-H "Authorization: Bearer <jwt-token>" \
-H "Content-Type: application/json" \
-d '{
"title": "Finish homework 1",
"description": "Complete exercises 1 through 10",
"dueDate": "2026-06-01",
"priority": "HIGH",
"status": "TODO"
}'Get tasks for one course:
curl http://localhost:8080/api/courses/1/tasks \
-H "Authorization: Bearer <jwt-token>"Search/filter/sort tasks:
curl "http://localhost:8080/api/tasks?title=homework&status=TODO&priority=HIGH&courseId=1&sort=dueDate" \
-H "Authorization: Bearer <jwt-token>"Sort tasks by priority:
curl "http://localhost:8080/api/tasks?sort=priority" \
-H "Authorization: Bearer <jwt-token>"Get one task:
curl http://localhost:8080/api/tasks/1 \
-H "Authorization: Bearer <jwt-token>"Update a task:
curl -X PUT http://localhost:8080/api/tasks/1 \
-H "Authorization: Bearer <jwt-token>" \
-H "Content-Type: application/json" \
-d '{
"title": "Finish homework 1",
"description": "Complete and review exercises 1 through 10",
"dueDate": "2026-06-03",
"priority": "MEDIUM",
"status": "IN_PROGRESS"
}'Delete a task:
curl -X DELETE http://localhost:8080/api/tasks/1 \
-H "Authorization: Bearer <jwt-token>"Create a grade for a course:
curl -X POST http://localhost:8080/api/courses/1/grades \
-H "Authorization: Bearer <jwt-token>" \
-H "Content-Type: application/json" \
-d '{
"assignmentName": "Midterm exam",
"score": 92,
"maxScore": 100,
"weight": 30
}'Get grades for a course:
curl http://localhost:8080/api/courses/1/grades \
-H "Authorization: Bearer <jwt-token>"Get one grade:
curl http://localhost:8080/api/grades/1 \
-H "Authorization: Bearer <jwt-token>"Update a grade:
curl -X PUT http://localhost:8080/api/grades/1 \
-H "Authorization: Bearer <jwt-token>" \
-H "Content-Type: application/json" \
-d '{
"assignmentName": "Midterm exam",
"score": 95,
"maxScore": 100,
"weight": 30
}'Delete a grade:
curl -X DELETE http://localhost:8080/api/grades/1 \
-H "Authorization: Bearer <jwt-token>"Test a Canvas connection:
curl -X POST http://localhost:8080/api/canvas/test \
-H "Authorization: Bearer <jwt-token>" \
-H "Content-Type: application/json" \
-d '{
"baseUrl": "https://school.instructure.com",
"accessToken": "<canvas-token>"
}'Sync Canvas data:
curl -X POST http://localhost:8080/api/canvas/sync \
-H "Authorization: Bearer <jwt-token>" \
-H "Content-Type: application/json" \
-d '{
"baseUrl": "https://school.instructure.com",
"accessToken": "<canvas-token>"
}'Load mock Canvas data without a token:
curl -X POST http://localhost:8080/api/canvas/mock-sync \
-H "Authorization: Bearer <jwt-token>"Get synced Canvas tasks:
curl "http://localhost:8080/api/canvas/tasks?bucket=HIGH_PRIORITY" \
-H "Authorization: Bearer <jwt-token>"Supported task buckets are ALL, TODAY, THIS_WEEK, OVERDUE, NO_DUE_DATE, and HIGH_PRIORITY.
Get recent Canvas sync logs:
curl http://localhost:8080/api/canvas/sync-logs \
-H "Authorization: Bearer <jwt-token>"Example validation error response:
{
"timestamp": "2026-05-26T12:00:00Z",
"status": 400,
"error": "Bad Request",
"message": "Validation failed",
"fieldErrors": {
"name": "Course name is required"
}
}Authentication errors return 401 Unauthorized.
Missing resources return 404 Not Found.
Duplicate registration email returns 409 Conflict.
Canvas rate limits return 429 Too Many Requests when Canvas sends that status.
Run all tests:
mvn testThe suite includes:
- Service-layer unit tests for courses, tasks, grades, and dashboard
- Canvas priority algorithm tests
- Mock Canvas sync and SQLite storage tests
- Integration tests for registration/login
- Integration tests for protected endpoint access and data ownership isolation
StudyFlow
├── .github/workflows/ci.yml
├── src
│ ├── main
│ │ ├── java/com/studyflow
│ │ │ ├── canvas
│ │ │ ├── config
│ │ │ ├── controller
│ │ │ ├── dto
│ │ │ ├── entity
│ │ │ ├── exception
│ │ │ ├── repository
│ │ │ ├── security
│ │ │ └── service
│ │ └── resources
│ │ ├── static
│ │ │ ├── app.js
│ │ │ ├── index.html
│ │ │ └── styles.css
│ │ └── application.properties
│ └── test
│ ├── java/com/studyflow
│ │ ├── canvas
│ │ ├── controller
│ │ └── service
│ └── resources/mockito-extensions
├── pom.xml
└── README.md
Created as a portfolio project to demonstrate practical backend development, RESTful API design, secure authentication, frontend integration, and maintainable test coverage.



