Student Feedback System is a web-based client-server application for student onboarding, survey operations, and internal feedback handling.
The current implementation supports:
- Student account registration
- Email verification through Resend
- Student document upload after verification
- Admin approval or rejection of pending student accounts
- Governed survey lifecycle with draft, publish, close, and archive states
- Reusable Question Bank and Survey Template management for admins
- Survey recipient tracking with denominator-based response metrics
- Scoped lecturer survey-result access for department-targeted surveys
- Authenticated survey listing, detail view, and submission
- Student survey visibility enforced in the backend using recipient rows, publish state, hidden flag, and end-date expiry
- Survey result viewing for admin and lecturer roles
- PDF/XLSX survey result export for admins
- Admin analytics dashboard with lifecycle, participation, and attention metrics
- Admin audit log viewer for privileged actions
- Persisted student notifications with read/unread state, WebSocket realtime delivery, and scheduled survey deadline reminders
- Shared authenticated frontend shell with role-aware navigation
- Account area for current-user overview and password management
- Admin user management with backend-backed search, filter, pagination, and sort
- Admin survey management with backend-backed search, filter, pagination, and sort
- Operational queue views for survey results, staff feedback review, and pending student review
- Backend: Java 21, Spring Boot 4, Spring MVC, Spring Security, Spring WebSocket, Spring Data JPA, Flyway
- Database: Microsoft SQL Server
- Authentication: JWT bearer tokens
- Email delivery: Resend API
- Frontend: React 19, TypeScript, Vite, React Router, Axios, STOMP/SockJS, Tailwind CSS
- CI: GitHub Actions
- Container images: Docker for backend and frontend
backend/Spring Boot API server. Organized around Hexagonal Architecture / Ports and Adapters.frontend/React web client that calls the backend API. Uses a shared authenticatedAppShell, role-aware navigation, shared UI primitives, and reusable operational data-view components.API_CONTRACT.mdImplemented API slices and payload expectations.docs/reporting-architecture.mdReporting and export boundaries for analytics queries and survey result exports.database/README.mdFresh setup, migration path, and demo credential notes..env.exampleBackend environment variable template.frontend/.env.exampleFrontend environment variable template.
- Java 21
- Node.js 22 and npm
- Microsoft SQL Server with the application schema already created
- A Resend account and API key if you want to test the real email verification flow
- A reachable MinIO instance for document storage
- Roboto font installed on the backend host for PDF export with Vietnamese text
Backend variables are documented in .env.example. Keep real credentials in your shell, deployment secret store, or IDE run configuration. Do not commit .env, .env.dev, .env.prod, or copied local secrets.
DB_URL=jdbc:sqlserver://localhost;databaseName=SURVEY_SYSTEM_DEV;encrypt=true;trustServerCertificate=true
DB_USERNAME=sa
DB_PASSWORD=change-me
APP_JWT_SECRET=change-me-local-dev-secret-key-32b
APP_JWT_ACCESS_TOKEN_EXPIRATION_MS=86400000
APP_VERIFY_EMAIL_URL_BASE=http://localhost:5173
APP_RESET_PASSWORD_URL_BASE=http://localhost:5173
APP_RESET_PASSWORD_EXPIRATION_MINUTES=30
RESEND_API_KEY=re_...
APP_MAIL_FROM=noreply@cuongdso.id.vn
RESEND_API_URL=https://api.resend.com/emails
APP_WEB_ALLOWED_ORIGINS=http://localhost:5173
APP_NOTIFICATIONS_SURVEY_REMINDER_CRON=0 */5 * * * *
APP_NOTIFICATIONS_SURVEY_REMINDER_ZONE=Asia/Ho_Chi_Minh
APP_NOTIFICATIONS_SURVEY_REMINDER_WINDOW_HOURS=24
APP_STORAGE_MINIO_ENDPOINT=http://localhost:9000
APP_STORAGE_MINIO_ACCESS_KEY=minioadmin
APP_STORAGE_MINIO_SECRET_KEY=minioadmin
APP_STORAGE_MINIO_BUCKET=student-documents
APP_AI_API_KEY=
RABBITMQ_HOST=localhost
RABBITMQ_PASSWORD=guest
APP_REPORTS_BIRT_TEMPLATE_PATH=classpath:/reports/survey_template.rptdesignFrontend variables from frontend/.env.example:
VITE_API_BASE_URL=/api
VITE_API_PROXY_TARGET=http://localhost:8080Notes:
- Spring Boot in this repo does not auto-load
.envfiles. Export variables in your shell or configure them in your IDE run configuration. .env.exampleis the canonical safe reference. Local.env.*files are ignored by Git and should stay out of the repository.- The backend document storage configuration is read from
app.storage.minio.*inbackend/src/main/resources/application.yaml, which means the Spring application needsAPP_STORAGE_MINIO_ENDPOINT,APP_STORAGE_MINIO_ACCESS_KEY, andAPP_STORAGE_MINIO_SECRET_KEY. spring.jpa.hibernate.ddl-auto=validateis enabled, so Flyway is responsible for creating or updating schema state before Hibernate validates entities.- Flyway is enabled through
spring.flyway.enabled=trueand scans SQL migrations fromclasspath:db/migration, which maps tobackend/src/main/resources/db/migration/. spring.flyway.baseline-on-migrate=trueis enabled so an existing SQL Server database can be brought under Flyway management without replaying the initial schema migration.backend/src/main/resources/db/migration/V1__initial_schema.sqlis the baseline schema migration used for new environments.backend/src/main/resources/db/migration/V2__notification_module.sqlextends notification storage with type, title, survey metadata, delivery timestamp, and read state.backend/src/main/resources/db/migration/V14__optimize_notification_query.sqladds a filtered unread-notification index for faster badge and unread-list queries.- For a new database, let Flyway apply the versioned scripts at startup. The legacy
database/full_schema.sqlanddatabase/migrations/files are still useful as reference SQL, but the application now runs migrations from the Flyway folder. - Resend must be configured if you want registration to send a real verification email. If
RESEND_API_KEYis missing, registration email delivery will fail by design.
Student onboarding documents are stored in MinIO through the backend storage adapter at MinioStudentDocumentStorageAdapter.java. The database keeps the bucket/object path, while the binary file itself lives in MinIO.
Variables involved:
APP_STORAGE_MINIO_ENDPOINT: Spring Boot endpoint for the MinIO SDK. In most deployments this matchesMINIO_URL.APP_STORAGE_MINIO_ACCESS_KEY: Spring Boot credential used by the backend MinIO client. In most deployments this matchesMINIO_ACCESS_KEY.APP_STORAGE_MINIO_SECRET_KEY: Spring Boot credential used by the backend MinIO client. In most deployments this matchesMINIO_SECRET_KEY.APP_STORAGE_MINIO_BUCKET: bucket name used for student documents.
Recommended local mapping:
APP_STORAGE_MINIO_ENDPOINT=http://localhost:9000
APP_STORAGE_MINIO_ACCESS_KEY=minioadmin
APP_STORAGE_MINIO_SECRET_KEY=minioadmin
APP_STORAGE_MINIO_BUCKET=student-documentsOperational notes:
- The backend only talks to MinIO through the
APP_STORAGE_MINIO_*variables. - If the backend starts without the
APP_STORAGE_MINIO_*values, document upload and document review endpoints cannot initialize correctly.
Flyway is the authoritative migration mechanism for the backend.
- Maven dependencies:
spring-boot-starter-flywayflyway-coreflyway-sqlserver
- Runtime configuration in
backend/src/main/resources/application.yaml:spring.flyway.enabled=truespring.flyway.baseline-on-migrate=truespring.flyway.locations=classpath:db/migration
- Migration directory:
- Naming convention:
V1__initial_schema.sqlV2__...sqlV3__...sql
How it works in this project:
- On backend startup, Spring Boot 4 auto-configures Flyway through the Flyway starter.
- Flyway connects to the same SQL Server datasource configured by
DB_URL,DB_USERNAME, andDB_PASSWORD. - On a fresh database, Flyway creates
flyway_schema_historyand applies the migration files in version order. - On an existing database, Flyway baselines the current schema first, then manages subsequent versioned migrations from that point forward.
- Hibernate runs in
validatemode after schema migration, so entity validation happens against the Flyway-managed schema.
Survey result exports use Eclipse BIRT through the outbound adapter at BirtSurveyReportRenderer.java. The renderer supports PDF and XLSX and receives a prepared EnterpriseSurveyReport; it does not query the database.
Template management:
- Active template:
backend/src/main/resources/reports/survey_template.rptdesign - Runtime property:
APP_REPORTS_BIRT_TEMPLATE_PATH=classpath:/reports/survey_template.rptdesign - Keep
.rptdesignfiles underbackend/src/main/resources/reports/so Spring can package them into the application JAR. - If template-driven schema changes are needed, add a new Flyway migration. Do not edit existing migration files.
Data flow:
SurveyResultController
-> SurveyReportExportService
-> SurveyResultPersistenceAdapter
-> SurveyReportDataAssembler
-> EnterpriseSurveyReport
-> BirtSurveyReportRenderer
-> BIRT appContext / in-memory template patch
-> PDF or XLSX bytes
The renderer populates BIRT appContext with the enterprise report, summary statistics, organization branding, filter metadata, report period, and question rows. For stable PDF/XLSX rendering, the current implementation hydrates dashboard, participation funnel, question table, and the bar chart fallback into the template at render time.
Chart fallback:
- Native BIRT chart XML is brittle across runtime and designer versions.
- The report uses a grid-based horizontal bar chart for the Top 5 question ratings.
- The fallback is deterministic in both PDF and XLSX and uses the same prepared POJO data as the rest of the report.
Font setup for Vietnamese PDF output:
- Install Roboto on the backend host before rendering reports.
- On Windows, install
Roboto-Regular.ttfand restart the backend process. - In containers, copy Roboto into the image and register it with the system font cache.
- The runtime-injected report styles force Roboto for consistent Vietnamese glyph rendering.
From backend/:
$env:DB_URL="jdbc:sqlserver://localhost;databaseName=SURVEY_SYSTEM_DEV;encrypt=true;trustServerCertificate=true"
$env:DB_USERNAME="sa"
$env:DB_PASSWORD="your-password"
$env:APP_JWT_SECRET="change-me-local-dev-secret-key-32b"
$env:APP_JWT_ACCESS_TOKEN_EXPIRATION_MS="86400000"
$env:APP_VERIFY_EMAIL_URL_BASE="http://localhost:5173"
$env:APP_RESET_PASSWORD_URL_BASE="http://localhost:5173"
$env:APP_RESET_PASSWORD_EXPIRATION_MINUTES="30"
$env:RESEND_API_KEY="re_..."
$env:APP_MAIL_FROM="noreply@cuongdso.id.vn"
$env:RESEND_API_URL="https://api.resend.com/emails"
$env:APP_WEB_ALLOWED_ORIGINS="http://localhost:5173"
$env:APP_NOTIFICATIONS_SURVEY_REMINDER_CRON="0 */5 * * * *"
$env:APP_NOTIFICATIONS_SURVEY_REMINDER_ZONE="Asia/Ho_Chi_Minh"
$env:APP_NOTIFICATIONS_SURVEY_REMINDER_WINDOW_HOURS="24"
$env:MINIO_ACCESS_KEY="your-access-key"
$env:MINIO_SECRET_KEY="your-secret-key"
$env:MINIO_BUCKET="student-feedback-bucket"
$env:MINIO_URL="http://localhost:9000"
$env:APP_STORAGE_MINIO_ACCESS_KEY="your-access-key"
$env:APP_STORAGE_MINIO_SECRET_KEY="your-secret-key"
$env:APP_STORAGE_MINIO_ENDPOINT="http://localhost:9000"
cd backend
.\mvnw.cmd spring-boot:runBackend default URL:
http://localhost:8080
From frontend/:
cd frontend
npm ci
npm run devFrontend default URL:
http://localhost:5173
The Vite dev server proxies /api requests to the backend target from frontend/vite.config.ts.
Backend tests:
cd backend
.\mvnw.cmd testFrontend lint and build:
cd frontend
npm run lint
npm run build- Open
/register - Create a student account with a department that already exists in the database
- Check the real email inbox and click the
/verify-email?token=...link sent by Resend - Sign in as that student
- Upload student card and national ID documents
- If admin rejects the onboarding request, sign in again, review the rejection feedback, and resubmit corrected documents
- Sign in again after admin approval
- Sign in with an admin account that already exists in the database
- Open
/admin/students/pending - Approve a pending student with optional reviewer notes, or reject with a required reason and optional reviewer notes
- Sign in with any authenticated role
- Open
/account - Review the account overview based on the current session data
- Open
/account/security - Change the current password
Compatibility note:
/change-passwordnow redirects to/account/security
- Sign in with an active student account
- Open
/surveys - View survey detail
- Submit answers for all required questions on surveys that have been published, are assigned to the current student, are not hidden, and have not expired
- Sign in with an admin account
- Open
/admin/surveys - Create a survey draft
- Optionally manage reusable questions in
/admin/question-bank - Optionally manage or apply reusable templates in
/admin/survey-templates - Edit the draft until dates, questions, and recipient scope are ready
- Publish the survey
- Monitor targeted, opened, submitted, and response-rate metrics
- Export result PDF/XLSX reports from survey result detail as admin
- Close it when collection should stop, then archive it when the run is complete
- Sign in with an admin account
- Open
/dashboard/adminfor survey lifecycle, participation, department, and attention metrics - Open
/admin/audit-logsto inspect successful privileged actions with filters and pagination - Sign in as a student and watch the notification bell in the app header
- Open
/notificationsto review survey and onboarding notifications, filter unread items, and mark items as read - Keep the student UI open while an admin publishes a survey or while a survey deadline reminder is generated; a realtime toast should appear and the bell badge should update without reload
- Sign in with an admin account
- Open
/admin/users - Use role segmentation, keyword search, filters, pagination, and sort controls
- Open a user detail record and perform supported status changes
- Sign in with an admin or lecturer account
- Open
/survey-results - Inspect survey statistics, participation metrics, and question-level breakdowns
- Sign in with an admin or lecturer account
- Open
/feedback/manage - Search and filter the feedback queue, open a queue item, and send a response
- As admin, open
/admin/students/pendingand work the review queue
- No root Docker Compose setup exists in this repo. Dockerfiles build images only.
- Registration depends on real email delivery. If verification emails are not arriving, check
RESEND_API_KEY,APP_MAIL_FROM, and that the sender domain is verified in Resend. - The frontend does not generate backend URLs on its own. Use
VITE_API_BASE_URLandVITE_API_PROXY_TARGETinstead of hardcoding API hosts. - Student onboarding relies on SQL Server lookup data. In particular, department names must exist in the
Departmenttable before registration succeeds. - If the backend fails on startup with Flyway errors, inspect
backend/src/main/resources/db/migration/and theflyway_schema_historytable first. - If the backend fails after Flyway succeeds with schema validation errors, the migrated schema still does not match the JPA mappings.
- Current account overview uses the authenticated session data already available to the frontend. It does not fabricate unsupported profile fields from a separate profile API.
- User management search, filter, pagination, and sort are backend-backed.
- Survey management search, filter, pagination, and sort are backend-backed.
- Question Bank, Survey Templates, pending-student review, staff feedback review, student surveys, student feedback history, notifications, audit logs, and survey results now use backend-backed pagination.
- Survey results also use backend-backed filtering, sorting, and metrics while exposing lifecycle, runtime status, and audience scope.
- Student survey completion state is derived from
Survey_Recipient.submitted_at, not a frontend-only flag. - Student survey list/detail responses prefer bilingual survey title and description columns when translated content exists.
- Student survey text responses publish
SURVEY_RESPONSEtranslation tasks after submit and persist bilingual comment columns onResponse_Detail. - Student notifications are persisted in SQL Server and delivered realtime over STOMP/SockJS when the student is online.
- The student header shows a notification bell with the unread count from
GET /api/v1/notifications/unread-count. - Realtime notification toasts are private user messages delivered through
/user/topic/notifications; clicking a toast marks it read and navigates to the relevant survey or onboarding page. - Notification deadline reminders are generated by
SurveyReminderTask, which runs onAPP_NOTIFICATIONS_SURVEY_REMINDER_CRONand skips duplicates for the same student, survey, type, and day. - Seed accounts in
database/seed_data.sqlare BCrypt-compatible and can be used directly after import:admin@university.edu/admin123lecturer@university.edu/lecturer123- seeded student accounts /
student123
- Frontend is a React + Vite + TypeScript SPA with:
- shared authenticated
AppShell - role-aware navigation groups
/accountand/account/security- shared page, state, badge, and data-view primitives
- operational admin pages built around tables and queues instead of flat card walls
- shared authenticated
- Frontend calls backend REST APIs over HTTP.
- Backend follows a Ports and Adapters structure:
adapter.in: web and security entry pointsapplication: use cases, domain models, input and output portsadapter.out: persistence adapters, security token service, external integrations such as Resend
- Main request flow: frontend page -> frontend API client -> backend controller -> use case service -> output port -> persistence adapter -> SQL Server
- Reporting follows the same boundary. Controllers do not own reporting SQL or export formatting; reporting queries live behind output ports in persistence adapters, and survey result export renders through a
SurveyReportRenderer. - See
docs/reporting-architecture.mdfor the current admin analytics and survey export flow.