AniMusic_BE is the backend service for the AniMusic ecosystem, a platform for managing, distributing, and using music visualization templates. The project is built as a modular monolith with Spring Boot: business domains are separated into modules while the system is still deployed as one backend application.
- Overview
- Tech Stack
- Project Structure
- Main Modules
- Key Business Flows
- Environment Requirements
- Environment Configuration
- Running the Project
- API Documentation
- Database and Migrations
- AI, RAG, and Audio Profile
- Testing
- Development Conventions
- Additional Documentation
The backend currently supports these major feature groups:
- Authentication, RBAC authorization, and user management.
- Template marketplace: creation, updates, review workflow, versioning, categories, tags, favorites, download tracking, and feedback/rating.
- Premium template checkout through PayOS and Stripe.
- AI chat with template-based RAG, SSE streaming, and audio profile extraction.
- Realtime render progress tracking through WebSocket/STOMP and REST history APIs.
- Realtime notifications and notification history.
- Admin dashboards, including template developer evaluation.
The application uses PostgreSQL as the primary database, Flyway for schema management, Cloudflare R2 for template archives, Cloudinary for images/thumbnails, and Spring AI for OpenAI/pgvector integration.
| Area | Technology |
|---|---|
| Runtime | Java 21, Spring Boot 3.4.5 |
| Build | Maven Wrapper |
| Persistence | Spring Data JPA, Hibernate, PostgreSQL |
| Migration | Flyway |
| Security | Spring Security, JWT with JJWT |
| Validation | Jakarta Validation |
| API docs | SpringDoc OpenAPI, Swagger UI |
| AI | Spring AI 1.0.x, OpenAI chat/embedding, Gemini through an OpenAI-compatible endpoint |
| Vector store | pgvector |
| Storage | Cloudflare R2, Cloudinary |
| Payment | PayOS SDK, Stripe Java SDK |
| Realtime | Spring WebSocket/STOMP, WebFlux SSE |
| Resilience | Resilience4j circuit breaker, rate limiter, retry |
| Test | JUnit 5, Mockito, H2, OpenCSV, JaCoCo |
src/main/java/vn/fpt/AniMusic_BE
├── config # Spring, AI, WebSocket, Swagger, Flyway, and seed configuration
├── exceptions # AppException, ErrorCode, GlobalExceptionHandler
├── infrastructure # Email, storage, payment config, websocket helper, slug service
├── modules # Main business modules
├── payload # ApiResponse<T>
└── security # JWT filter/provider, principal, CORS, SecurityConfig
src/main/resources
├── application.yaml # Default profile and .env import
├── application-dev.yaml # Development configuration
├── application-prod.yaml # Production configuration
├── chat # Python audio profile extractor
├── db/migration # Flyway migrations
└── templates # Email templates
Most business modules follow this structure:
controller/
service/
repository/
entity/
dto/
mapper/
Some foundational modules, such as authority, focus mainly on entities, repositories, and enums used by RBAC.
| Module | Responsibility |
|---|---|
authenticate |
Login, registration, refresh token, email confirmation, forgot/reset password, app login code |
authority |
Roles, permissions, and RBAC mappings |
user |
User profile, password change, avatar upload, admin user management |
template |
Template lifecycle: create, update, review, list/detail, download, delete |
template_version |
Template version management |
template_upload |
Multipart upload orchestration for template archives on Cloudflare R2 |
template_category |
Category CRUD and filtering |
template_tag |
Tag CRUD and tag-template mappings |
template_favorite |
Favorite/unfavorite and user favorite listing |
template_download |
Download event tracking and success/failed confirmation |
template_feedback |
Template-level feedback and rating |
cart |
Pre-checkout cart operations |
order |
Order lifecycle and lookup |
payment |
Payment orchestration, PayOS/Stripe gateways, and webhooks |
chat |
Conversations, messages, AI chat, SSE streaming, RAG, audio profile |
notification |
Notification APIs and read/unread state |
dashboard |
System analytics and template developer evaluation |
render |
Render progress ingest through WebSocket and REST history APIs |
The backend supports two ways to upload template archives:
- Direct multipart form upload through the template/template-version APIs.
- Multipart presigned upload through
/template-uploads/init, client-side part uploads to R2, and completion through/template-uploads/complete.
Regular users normally create or update templates into PENDING_REVIEW. Staff/Admin users can review templates and approve or reject them. Review/delete events are used to send notifications and synchronize the RAG index.
GET /templates/{id}/download only issues a presigned URL and creates a downloadEventId; it does not increment download_count.
The client must call POST /templates/{templateId}/downloads/{downloadEventId}/confirm to confirm SUCCESS or FAILED. Only the first SUCCESS confirmation increments templates.download_count; repeated confirmations are handled idempotently.
Feedback is template-level. A user can leave one feedback entry per template only after at least one successful download. The denormalized rating_count and rating_avg values are synchronized whenever feedback is created, updated, or deleted.
Main checkout flow:
cart/items -> create PENDING order -> select PAYOS or STRIPE gateway -> receive checkout URL -> webhook confirmation -> complete payment/order
Available webhook endpoints:
- PayOS:
/payments/webhook/payos - PayOS webhook URL confirmation:
/payments/webhook/payos/confirm - Stripe:
/payments/webhook/stripe
AI chat uses Spring AI. Metadata from APPROVED and PUBLIC templates is converted into vector documents in pgvector and retrieved as contextual data for chat responses. Both non-streaming and SSE streaming chat are supported.
The audio profile extractor runs as a Python script at src/main/resources/chat/audio_profile_extractor.py. Extracted audio profile data is runtime-only and is not persisted to the database.
The desktop client sends render progress updates through the WebSocket destination /app/render/update. The backend stores render processes and samples, then exposes REST APIs for authenticated users to view render history.
- JDK 21.
- Maven Wrapper, already included in the repository.
- Docker and Docker Compose if running with containers.
- PostgreSQL with the pgvector extension if running the database outside Docker.
- Python 3 if using the audio profile extractor outside Docker.
Python packages required by the audio extractor are listed in requirements.txt:
librosa
numpy
soundfile
scipy
setuptools
The application reads .env through this Spring configuration:
spring.config.import: optional:file:.env[.properties]Create a .env file in the project root. Minimal local development example:
SPRING_PROFILES_ACTIVE=dev
CLIENT_URL=http://localhost:5173
DATABASE_NAME=animusic
DATABASE_URL=jdbc:postgresql://localhost:5433/animusic
DATABASE_USERNAME=animusic
DATABASE_PASSWORD=animusic
APP_ADMIN_USERNAME=admin
APP_ADMIN_EMAIL=admin@example.com
APP_ADMIN_PHONE=0900000001
APP_ADMIN_PASSWORD=ChangeMe123!
APP_USER_USERNAME=user
APP_USER_EMAIL=user@example.com
APP_USER_PHONE=0900000002
APP_USER_PASSWORD=ChangeMe123!
APP_USER_DEFAULT_AVATAR=https://example.com/avatar.png
APP_STAFF_USERNAME=staff
APP_STAFF_EMAIL=staff@example.com
APP_STAFF_PHONE=0900000003
APP_STAFF_PASSWORD=ChangeMe123!
APP_DEVELOPER_USERNAME=developer
APP_DEVELOPER_EMAIL=developer@example.com
APP_DEVELOPER_PHONE=0900000004
APP_DEVELOPER_PASSWORD=ChangeMe123!
CORS_ALLOWED_ORIGINS=http://localhost:5173
CORS_ALLOWED_METHODS=GET,POST,PUT,PATCH,DELETE,OPTIONS
CORS_ALLOWED_HEADERS=*
EMAIL_USERNAME=your-email@gmail.com
EMAIL_PASSWORD=your-app-password
EMAIL_VERIFICATION_EXPIRATION=3600
JWT_SECRET_KEY=replace-with-a-long-random-secret-key
JWT_DURATION=3600
JWT_REFRESH_DURATION=604800
CLOUDFLARE_R2_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com
CLOUDFLARE_R2_ACCESS_KEY=your-r2-access-key
CLOUDFLARE_R2_SECRET_KEY=your-r2-secret-key
CLOUDFLARE_R2_BUCKET=your-bucket
CLOUDFLARE_R2_DOWNLOAD_PRESIGNED_URL_DURATION=3600
CLOUDINARY_CLOUD_NAME=your-cloud-name
CLOUDINARY_API_KEY=your-api-key
CLOUDINARY_API_SECRET=your-api-secret
PAYOS_CLIENT_ID=your-payos-client-id
PAYOS_API_KEY=your-payos-api-key
PAYOS_CHECKSUM_KEY=your-payos-checksum-key
PAYOS_RETURN_URL=http://localhost:5173/payment/success
PAYOS_CANCEL_URL=http://localhost:5173/payment/cancel
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
STRIPE_RETURN_URL=http://localhost:5173/payment/success
STRIPE_CANCEL_URL=http://localhost:5173/payment/cancel
OPENAI_API_KEY=sk-xxx
GOOGLE_AI_API_KEY=
APP_LOGIN_CODE_EXPIRATION=300
APP_SEED_ENABLED=trueNotes:
- Do not commit
.envor real secrets. - Leave
GOOGLE_AI_API_KEYempty if Gemini is not used. - If audio profile extraction is not needed locally, avoid calling the audio endpoint from the client. Otherwise, install the Python dependencies.
- For RAG, the embedding dimension must stay synchronized across Spring AI configuration, pgvector configuration, and database migration definitions.
Start PostgreSQL pgvector:
docker compose up -d postgresRun the backend:
./mvnw spring-boot:runOn Windows PowerShell:
.\mvnw.cmd spring-boot:runThe application runs by default at:
http://localhost:8080
docker compose up --buildDocker Compose builds the backend image, installs the Python runtime for the audio extractor, starts PostgreSQL pgvector, and exposes the backend on port 8080.
./mvnw clean packageRun the jar:
java -jar target/AniMusic_BE-0.0.1-SNAPSHOT.jarSwagger UI:
http://localhost:8080/swagger-ui.html
OpenAPI JSON:
http://localhost:8080/v3/api-docs
Main endpoint groups:
| Group | Base path |
|---|---|
| Auth | /auth |
| Users | /users |
| Templates | /templates |
| Template upload | /template-uploads |
| Template versions | /template-versions |
| Template categories | /template-cats |
| Template tags | /template-tags |
| Template favorites | /template-favorites |
| Cart | /carts |
| Orders | /orders |
| Payments | /payments |
| Chat | /chat |
| Chat admin | /chat/admin |
| Notifications | /notifications |
| Dashboard | /dashboards |
| Render history | /render-processes |
| WebSocket docs | /ws-docs |
WebSocket endpoint:
/ws
STOMP conventions:
- Application destination prefix:
/app - User destination prefix:
/user - Simple broker prefix:
/queue
Flyway migrations are stored in:
src/main/resources/db/migration
Migration naming convention:
V{number}__description.sql
Important constraints:
spring.jpa.hibernate.ddl-auto=none; schema changes are managed by Flyway.open-in-view=false; lazy data must be fetched or initialized explicitly in services.- The pgvector schema is owned by migrations, not by Spring AI automatic initialization.
- The vector embedding column must use the same dimension as:
spring.ai.openai.embedding.options.dimensionsspring.ai.vectorstore.pgvector.dimensions- the database column definition.
Main chat APIs:
POST /chat/send: non-streaming response.POST /chat/send/stream: SSE stream.POST /chat/audio/keywords: extracts audio keywords/profile.POST /chat/admin/reindex: admin-triggered RAG index rebuild.
RAG only indexes and returns templates that are:
APPROVEDPUBLIC
Audio extractor configuration:
- Script:
src/main/resources/chat/audio_profile_extractor.py - Python path:
APP_CHAT_AUDIO_PYTHON_PATH - Script path override:
APP_CHAT_AUDIO_PYTHON_SCRIPT_PATH - Default limits: 25 MB, 180 seconds, 12 keywords.
Run all tests:
./mvnw testRun tests with coverage report generation:
./mvnw verifyThe test profile uses H2 and CSV fixtures under:
src/test/resources/testdata
Tests can use CsvTestDataLoader to load module-specific fixtures.
- Use
ResponseEntity<ApiResponse<T>>for regular REST APIs. - Use
Flux<ChatResponse>for chat SSE streaming. - Use
AppExceptionor module-specific exceptions withErrorCode; avoid raw string error responses. - Check
ErrorCode.javabefore adding a new error type. - Use DTO validation with
jakarta.validation. - Keep entity/DTO mapping in dedicated mapper classes.
- Keep Swagger annotations on controller endpoints.
- Apply
@PreAuthorizeconsistently for method-level RBAC. - Apply
@RateLimiterto public or sensitive endpoints. - Add a new Flyway migration for schema changes.
- Add or update tests when behavior changes.
- Preserve both template upload paths: direct multipart and multipart presigned upload.
- Do not increment
templates.download_countwhen only issuing a download URL; increment it only after a download event is confirmed asSUCCESS. - Keep
rating_countandrating_avgsynchronized whenever feedback changes.
docs/cloudflare-r2-multipart-upload.md: Cloudflare R2 multipart upload checklist for CORS, lifecycle cleanup, and validation.AGENTS.md: architecture and convention notes for AI agents working in this repository.