Thank you for your interest in contributing to learn-dev, a learning platform that uses hands-on practice, automated assessment and real-time feedback to teach programming.
Whether you're fixing a bug, proposing a new feature, improving documentation, or writing tests, every contribution helps make this project better. This guide will walk you through the process of contributing.
If you have any questions, feel free to open an issue on GitHub.
This page is quite long, so use the hamburger menu
to navigate the table of contents more easily.
You can find it at the top right of the page (see below).
Make sure you read and agree to our Code of Conduct.
Read the Prerequisites section of the README.
The code reference documentation is not yet available and will be added to this repository in a future update.
learn-dev is a Web-based client-server application using a PostgreSQL database to persist information.
graph TD
U["👤 User (Browser)"]
subgraph FE["Frontend (React / TypeScript)"]
Pages["Pages"]
Features["Features (auth, course…)"]
Components["Shared Components"]
end
subgraph BE["Backend (Spring Boot / Java)"]
Controllers["REST Controllers"]
Services["Services"]
Repositories["Repositories"]
Security["Spring Security (JWT)"]
end
DB[("PostgreSQL Database")]
U -->|"HTTPS (JSON)"| FE
FE -->|"REST API calls"| Controllers
Controllers --> Security
Security --> Services
Services --> Repositories
Repositories -->|"SQL"| DB
sequenceDiagram
actor U as User
participant FE as Frontend
participant API as Backend REST API
participant DB as Database
U->>FE: Enter email + password, click Login
FE->>API: POST /auth/login<br/>{ email, password }
API->>API: Validate request body (server-side validation)
alt Validation fails
API-->>FE: 400 Bad Request<br/>{ message: [...errors] }
FE-->>U: Show validation errors
end
API->>DB: SELECT user WHERE login = ? AND email = ?
alt User not found
API-->>FE: 401 Unauthorized
FE-->>U: "Invalid credentials"
end
API->>API: Verify Password
alt Password mismatch
API-->>FE: 401 Unauthorized
FE-->>U: "Invalid credentials"
end
API->>API: generate JWT { sub: uuid, role }
API-->>FE: 200 OK<br/>{ token: "eyJ..." }
FE->>FE: Store token (HTTP-only cookie)
FE-->>U: Redirect to dashboard (by role)
We use a monorepo, that is a Git repository containing mainly both the frontend and the backend.
Using a monorepo offers the following advantages:
- Shared code and types:
The frontend and backend can share types from a single source of truth, avoiding duplication and keeping them in sync. - Atomic changes:
A single commit or PR can update the database schema, backend API, and frontend together, ensuring they stay compatible. - Easier code reviews:
Reviewers can see the full picture of a change (database + backend + frontend) in a single PR rather than coordinating across multiple repositories. - Consistent tooling and configuration:
Linting rules, formatting, CI/CD pipelines, and Git hooks are configured once and apply to all packages.
The project is composed of 2 modules:
backend: Spring Boot app (inbackend/)frontend: React app (infrontend).
frontenddepends onbackend.
The frontend uses the JS types from and generated by the backend
out of the DTOs (such as UserResponse and CourseResponse).
This nifty trick makes the backend the single source of truth.
learn-dev/
├── .gitignore
├── README.md
│
├── backend/ # Spring Boot application
│ ├── pom.xml # Project’s dependencies, build configuration, and metadata
│ │
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/dev/ericbouchut/learndev/
│ │ │ │ ├── LearnDevApplication.java # Spring Boot entry point
│ │ │ │ │
│ │ │ │ ├── common/ # Concerns shared across multiple features
│ │ │ │ │ ├── config/
│ │ │ │ │ │ ├── SecurityConfig.java # Spring Security filter chain, CORS, CSRF
│ │ │ │ │ │ └── WebConfig.java # Global web configuration
│ │ │ │ │ ├── dto/
│ │ │ │ │ │ └── ApiErrorResponse.java # Standardized error response body
│ │ │ │ │ └── exception/
│ │ │ │ │ ├── GlobalExceptionHandler.java # @RestControllerAdvice for centralized error handling
│ │ │ │ │ └── ResourceNotFoundException.java
│ │ │ │ │
│ │ │ │ ├── auth/ # Authentication and token management
│ │ │ │ │ ├── AuthController.java # /api/auth endpoints (login, register, refresh)
│ │ │ │ │ ├── AuthService.java
│ │ │ │ │ ├── CustomUserDetailsService.java # Loads user from DB for Spring Security
│ │ │ │ │ ├── dto/
│ │ │ │ │ │ ├── LoginRequest.java
│ │ │ │ │ │ ├── RegisterRequest.java
│ │ │ │ │ │ └── AuthResponse.java
│ │ │ │ │ ├── entity/
│ │ │ │ │ │ ├── EmailVerificationToken.java
│ │ │ │ │ │ ├── PasswordResetToken.java
│ │ │ │ │ │ └── RefreshToken.java
│ │ │ │ │ ├── repository/
│ │ │ │ │ │ ├── EmailVerificationTokenRepository.java
│ │ │ │ │ │ ├── PasswordResetTokenRepository.java
│ │ │ │ │ │ └── RefreshTokenRepository.java
│ │ │ │ │ └── exception/
│ │ │ │ │ ├── InvalidCredentialsException.java
│ │ │ │ │ └── TokenExpiredException.java
│ │ │ │ │
│ │ │ │ ├── user/ # User profile and account management
│ │ │ │ │ ├── UserController.java # /api/users endpoints (CRUD, profile)
│ │ │ │ │ ├── UserService.java
│ │ │ │ │ ├── entity/
│ │ │ │ │ │ └── User.java # Maps to the users table
│ │ │ │ │ ├── repository/
│ │ │ │ │ │ └── UserRepository.java
│ │ │ │ │ ├── dto/
│ │ │ │ │ │ ├── UserResponse.java # Outbound DTO (never exposes password hash)
│ │ │ │ │ │ └── UpdateUserRequest.java
│ │ │ │ │ └── exception/
│ │ │ │ │ ├── UserNotFoundException.java
│ │ │ │ │ ├── DuplicateEmailException.java
│ │ │ │ │ └── AccountLockedException.java
│ │ │ │ │
│ │ │ │ ├── role/ # Role and permission management
│ │ │ │ │ ├── RoleController.java # /api/roles endpoints (admin only)
│ │ │ │ │ ├── RoleService.java
│ │ │ │ │ ├── entity/
│ │ │ │ │ │ ├── Role.java # Maps to the roles table
│ │ │ │ │ │ └── UserRole.java # Maps to the user_roles junction table
│ │ │ │ │ ├── repository/
│ │ │ │ │ │ ├── RoleRepository.java
│ │ │ │ │ │ └── UserRoleRepository.java
│ │ │ │ │ └── dto/
│ │ │ │ │ ├── RoleResponse.java
│ │ │ │ │ └── AssignRoleRequest.java
│ │ │ │ │
│ │ │ │ └── audit/ # Security audit trail
│ │ │ │ ├── AuditService.java # Internal use only (no controller)
│ │ │ │ ├── entity/
│ │ │ │ │ └── AuditLog.java # Maps to the audit_logs table
│ │ │ │ └── repository/
│ │ │ │ └── AuditLogRepository.java
│ │ │ │
│ │ │ └── resources/
│ │ │ ├── application.yml # Main config (active profile, app name)
│ │ │ ├── application-dev.yml # Dev profile (local DB, debug logging)
│ │ │ ├── application-prod.yml # Prod profile (external DB, stricter security)
│ │ │ └── db/
│ │ │ └── changelog/ # Liquibase migration files (when introduced)
│ │ │ └── db.changelog-master.yaml
│ │ │
│ │ │
│ │ └── test/
│ │ └── java/dev/ericbouchut/learndev/
│ │ ├── LearnDevApplicationTests.java # Context load smoke test
│ │ │
│ │ ├── auth/
│ │ │ ├── AuthControllerTest.java # @WebMvcTest for auth endpoints
│ │ │ └── AuthServiceTest.java
│ │ │
│ │ ├── user/
│ │ │ ├── UserControllerTest.java
│ │ │ ├── UserServiceTest.java
│ │ │ └── UserRepositoryTest.java # @DataJpaTest with Testcontainers
│ │ │
│ │ ├── role/
│ │ │ ├── RoleServiceTest.java
│ │ │ └── RoleRepositoryTest.java
│ │ │
│ │ └── audit/
│ │ └── AuditServiceTest.java
│ │
│ │
│ └── postman/ # Contains the Postman requests to test the REST endpoints
│ ├── learndev.environment.json # Postman environment file with placeholders (adjust to your local config.)
│ └── learndev.collection.json # Collection of Postman requests, with folders per feature
│
│
└── frontend/ # React frontend
├── package.json # Project’s dependencies, scripts, and metadata
├── .env.example
├── index.html # Vite's HTML entry point
├── vite.config.ts
├── tailwind.config.ts
├── tsconfig.json
├── package-lock.json # Locks down exact dependency versions for reproducible installs
├── public/ # Static assets served as-is by Vite
│ └── favicon.ico
└── src/
├── main.tsx # Vite entry point, mounts the React app
├── index.css # Tailwind directives
├── App.tsx # Root component, sets up routing
│
├── components/ # Generic, domain-agnostic UI components (primitives)
│ ├── Button.tsx
│ ├── Modal.tsx
│ └── Spinner.tsx
│
├── shared/ # Domain-aware components used across features
│ ├── User.tsx # Domain-aware component that wraps YouTubeEmbed
│ └── UserAvatar.tsx
│
├── pages/ # Thin routing shells only that assemble features
│ ├── HomePage.tsx
│ ├── LoginPage.tsx
│ ├── JuryDashboardPage.tsx
│ └── AdminDashboardPage.tsx
│
└── features/ # Feature-based modules (auth, course...), each self-contained
│
├── auth/ # Login, registration, JWT token management
│ ├── components/ # React components that belong exclusively to THIS feature
│ │ ├── LoginForm.tsx
│ │ └── RegisterForm.tsx
│ ├── hooks/ # Custom React hooks that manage state and behavior for this feature
│ │ └── useAuth.ts # Manage what happens when the user interacts with the UI (logic but no JSX).
│ ├── api/ # Communication with the backend REST endpoints (Data Access)
│ │ └── authApi.ts
│ └── types.ts # UI-only types: form values, token payload shape
│
├── course/ # Course gallery and detail
│ ├── components/
. │ ├── CourseGallery.tsx
. │ ├── CourseDetail.tsx
. │ └── CourseFilter.tsx
├── hooks/
│ └── useCourseGallery.ts
├── api/
│ └── courseApi.ts
└── types.ts # UI-only: filter state, pagination shapeThe table below explains what each folder entails.
| Folder | Purpose |
|---|---|
backend/src/main/java/…/learndev/ |
Spring Boot application root — entry point and top-level package |
backend/src/main/java/…/learndev/common/ |
Cross-cutting concerns: security config, global error handling, shared DTOs |
backend/src/main/java/…/learndev/auth/ |
Authentication and token management (login, register, JWT, refresh) |
backend/src/main/java/…/learndev/user/ |
User profile and account management |
backend/src/main/java/…/learndev/role/ |
Role and permission management |
backend/src/main/java/…/learndev/audit/ |
Security audit trail (internal use — no public controller) |
frontend/src/features/ |
Feature-based modules (auth, course…), each self-contained |
frontend/public/ |
Static Assets served as-is by Vite |
frontend/src/components/ |
Generic, domain-agnostic UI components (primitives) |
frontend/src/shared/ |
Domain-aware components used across features |
frontend/src/pages/ |
Thin routing shells only that assemble features |
frontend/src/features/course/types.ts |
UI-only types for this feature (course) |
frontend/src/features/course/components/ |
React components that belong exclusively to this feature |
frontend/src/features/course/hooks/ |
Custom React hooks that manage state and behavior for this feature |
frontend/src/features/course/api |
Communication with backend REST endpoints related to this feature in order to fetch data |
Note
Each backend feature folder (e.g. auth/, user/, role/) follows the same internal layout:
a controller, a service, an entity, a repository, DTOs, and an exceptions sub-package.
- Folder: snake_case
- Files
- Backend
- PascalCase: Classes use PascalCase and are suffixed by their
layer role:
CourseController,CourseService,Course,CourseRepository,CourseResponse:- Configuration classes end with
Config. - Exception classes end with
Exception. - Entity classes have no suffix (they represent the domain noun directly) (e.g.,
Course). An entity represents a database table as a Java object. It defines what the data looks like: which fields exist, their types, their constraints.
For example,Course.javamaps to yourcoursestable and contains fields likename,description. - Repository classes define how you access data such as finding a course by name,
saving a new course, deleting a course. For example,
CourseRepository.javamay provide methods such asfindByName(String name)orsave(Course course). - Service classes contains business logic.
- DTO data transfer classes carry data between layers or across the API boundary. They decouple the API's input/output shapes from the database entities and prevent sensitive fields (e.g. password hash) from leaking into responses.
- Controller: Spring
@RestControllers that maps HTTP requests to handler methods. They delegate business logic to the service layer and returns the appropriate HTTP response and status code. - ...
- Configuration classes end with
- Multi-part-naming for files in a feature folder:
name.type.extension, contains 3 segments, where:namemay refer to a feature, middleware, service,typerefers to the type:routes,controller,validation(JSON validation),service(handles business logic),middleware(intercepts requests before the handler — e.g., auth, logging),client(adapter for an external service)extensionrefers to the file extension such asts
- PascalCase: Classes use PascalCase and are suffixed by their
layer role:
- Frontend
- PascalCase for React components.
The filename (CourseCard.tsx) and the JSX component name (<CourseCard />) use PascalCase. - camelCase for everything else.
Any file that does not export a React component uses camelCase.
- PascalCase for React components.
- Backend
Tip
FRONTEND naming convention:
If the file's default export is a React component — use PascalCase.
For everything else — use camelCase.
Note
What are PascalCase and camelCase?
- PascalCase is a naming convention where the first letter of every word
> is capitalized, with no spaces or underscores between words:
YouTubeEmbed. - camelCase is a naming convention where the first word starts with a lowercase
> letter and each subsequent word begins with an uppercase letter, with no spaces or underscores:
useJuryVote.
This section describes how the database is structured. The learn-dev platform uses a PostgreSQL relational database to persist entities.
Here are the naming conventions for the name of our database tables:
- All lowercase
- Plural
- Use underscore for multi words names:
social_networks - Less than 64 characters (remnant of a MySQL constraint, just in case ;-)
Do not use an underscore as the first character.
The Entity Relationships Diagram (ERD) is available as an SVG image
Note
This diagram uses Crow's foot notation for the cardinality of relationships, where:
o|denotes0..1(zero or one)||denotes Exactly oneo{denotes0..n(zero or more)
Tip
If you want to create an Entity Relationship Diagram (ERD) like this one, then take a look at Mermaid.js. With this syntax embedded in a Markdown file, GitHub issue, you can easily create many types of diagrams such as sequence/flow/class/state diagrams to only name a few.
GitHub among many other tools, IDEs and platforms support Mermaid diagrams.
To give Mermaid diagrams a whirl, you can use the free online visual editor to build your first diagram, share it with others and even export it to various formats.
If you encounter a bug, please open an issue and include:
- A clear and descriptive title.
- Steps to reproduce the problem (code snippets, commands, or screenshots if relevant).
- What you expected to happen.
- What actually happened (including any error messages).
- Your environment details (OS, browser, Node/Python/runtime versions, etc. if applicable).
This information helps maintainers reproduce and fix the issue more quickly.
We welcome ideas to improve learn-dev (new features, UX improvements, performance, documentation, etc.). Before opening a feature request, please:
- Check the existing issues to see if a similar idea already exists.
- Add a comment to an existing issue if it matches your idea, rather than opening a duplicate.
If you open a new enhancement issue, try to explain the problem you are trying to solve, not only the solution you have in mind.
When creating a feature request issue, you can use the following structure:
- Summary: A short description of the feature.
- Problem: What problem does this feature solve for users?
- Proposed solution: How you think this feature could work (UI/API/behavior).
- Alternatives: Any alternative solutions or workarounds you have considered.
- Additional context: Screenshots, diagrams, or links that help explain the idea.
If you are new to the project, we recommend starting with issues labeled
good first issue or
help wanted on GitHub.
Steps to get started:
- Pick an open issue with one of these labels.
- Comment on the issue to indicate that you would like to work on it.
- Wait for a maintainer to confirm or provide additional guidance before investing a lot of time.
If you are unsure where to start, you can also open an issue asking for pointers or clarification.
At a high level, you will usually need to:
- Fork the repository and clone your fork locally.
- Follow the setup instructions in the project’s README files (for backend, frontend, or other components).
- Install the required dependencies using the package manager(s) mentioned there.
- Run the test suite or basic checks to ensure everything works before you start making changes.
For more detailed, component-specific instructions, please refer to the corresponding README files in each subdirectory.
The repository ships two files under backend/postman/ that let you
send requests to the backend API directly from Postman:
| File | Purpose |
|---|---|
learndev.collection.json |
All API requests, grouped by feature |
learndev.environment.json |
Variables with placeholder values (no real credentials) |
- Open Postman.
- Click
Collections/Import. - Select
backend/postman/learndev.collection.json.
- Click
Environments/Import. - Select
backend/postman/learndev.environment.json. - Select learnDev – Local as the active environment (top-right dropdown).
Open the learnDev – Local environment in Postman and fill in the fields marked as placeholders:
| Variable | What to set |
|---|---|
baseUrl |
URL of your local backend server (default: http://localhost:3000) |
Warning
Never commit real credentials. The environment file intentionally ships
with empty secret fields (authToken, loginPassword). Fill them in
locally; Postman keeps them on your machine only.
Most endpoints require a JWT. To obtain one:
- Run
Auth/Login(POST /auth/login). - Copy the
tokenvalue from the response body. - Paste it into the
authTokenenvironment variable.
All subsequent requests that require authentication read {{authToken}} from
the environment automatically.
Note
This project uses a very lightweight version of Git Flow (without release branch) branching workflow to develop, integrate, release new features, and fix bugs.
Why did we make this change?
Usually, main is the default branch and serves both for the "integration" and deployment to production,
which makes these processes brittle.
What is this change about?
To facilitate the integration of several features, we created the dev branch.
This is where we share the common code with the team.
It also serves as an integration branch to ensure that all the merged-in features work properly.
This new branching scheme makes dev the new default branch.
We use a monorepo, meaning it contains both the frontend and the backend.
gitGraph
commit
branch dev
checkout dev
branch feat/add-home-page
checkout feat/add-home-page
commit
commit
checkout dev
merge feat/add-home-page
branch feat/add-footer
commit
commit
checkout dev
merge feat/add-footer
checkout main
merge dev
commit type: HIGHLIGHT tag: "bug found"
checkout main
branch fix/htaccess
commit
checkout main
merge fix/htaccess
checkout dev
merge fix/htaccess
The branches:
devis the main development branch.- The repository uses this branch as the primary one for development and Pull Requests.
- It acts as an "integration" branch contains the common code and serves as a safety net.
- This branch contains the code shared with the team.
- This is the default branch, meaning it is the one we are on after cloning this repository, the base branch of our PRs.
- It is also where we test that the merged features do not break the website.
Once we are confident the code ondevcan be deployed to production, we mergedevintomain. - We should not commit directly to
dev, but create a PR (Pull Request) to bring in changes. - We have configured
devto require two approvals before merging todev.
maincontains the production-ready code.- This is where the team merges
devafter ensuring that the new features ondevare working properly together. - This branch is used for deployment.
- This is where the team merges
You create temporary branches to develop a feature or fix a bug:
feat/add-home-pagedenotes a feature branch.
The naming convention may seem a bit convoluted, but refer to:- the kind of branch it is:
feat(this is a feature), - what the branch will bring when merged to
devsuch asadd-home-page, a concise description of the feature, using all-lowercase words separated with an hyphen.
- the kind of branch it is:
fix/htaccess(orfix/broken-link-page-footer) are bug fix branches created when the website breaks in production.
They are branched off frommain, then merged intomainto fix the bug and thendevto backport the fix to the main development branch.
Note
dev is the base branch of PRs.
This is where GitHub merges the head branch, such as a
feature branch (feat/add-home-page) or a bugfix branch (fix/broken-link-page-footer).
---
title: GitHub PR branches
config:
gitGraph:
showCommitLabel: false
mainBranchName: "dev (base branch)"
---
gitGraph
commit
commit
branch "feat/add-home-page (head branch)"
checkout "feat/add-home-page (head branch)"
commit
commit
checkout "dev (base branch)"
merge "feat/add-home-page (head branch)" type: HIGHLIGHT
This project follows the Conventional Commits specification.
Format: <type>(<scope>): <description>
-
Type— Nature of the change (required):Type When to use featA new feature fixA bug fix docsDocumentation-only changes styleFormatting or whitespace — no logic change refactorCode change that neither adds a feature nor fixes a bug testAdding or correcting tests choreBuild process, tooling, or dependency updates -
Scope— Optional area of the codebase in parentheses (e.g.,auth,README,git). -
Description— Short imperative-mood summary, capitalized, no trailing period.
Examples:
feat(auth): Add JWT refresh token rotation
fix(user): Return 404 when user is not found
docs(README): Add prerequisites section
test(course): Cover edge case for empty course list
chore(git): Ignore IntelliJ IDEA configuration files
TODO: Document the code style and formatting
This section explains how to reset the development database. It may prove useful when you need to start from a blank slate.
TODO: Add how to reset the development database
The command above:
- Drops then recreates the database schema that is the structure (tables...),
- Applies all database migrations to recreate the database changes in chronological order,
- Runs a seed file to populate the database with development data.
Once you have picked up the latest changes from the upstream dev branch,
you need to apply the latest database migrations as follows:
TODO: How to apply the latest database migrations in development
To add, remove, an entity or a field/property you need to update the database schema. It serves as a source of truth used to generate and update the database.
Here is the workflow to add/update/remove the database structure (table, table column, relationship, enum value).
TODO: Explain how and where to update the database schema
We use different package/dependencies managers on the backend and the frontend:
Mavenon the backendnpmon the frontend
-
Search for the artifact on Maven Central.
-
Copy the
<dependency>snippet (select the Maven tab). -
Paste it inside the
<dependencies>block ofbackend/pom.xml:<dependency> <groupId>com.example</groupId> <artifactId>some-library</artifactId> <version>1.2.3</version> </dependency>
Omit
<version>when the dependency is already managed by the Spring Boot parent POM (e.g.,spring-boot-starter-*,jackson-databind). -
Save
pom.xml. Maven downloads the artifact automatically on the next build or IDE sync. -
Verify the dependency resolves correctly:
cd backend && mvn dependency:resolve
The example below explains how to add the following dependencies (npm packages)
to the frontend:
- Runtime dependencies
reactreact-dom
- Development dependency:
@types/react
cd frontend
# IMPORTANT: first install dependencies with npm
# Run npm install (one-shot) to install the existing project's dependencies
npm install
npm install react react-dom
npm install -D @types/reactWhere:
- The first
npm installline, adds thereactandreact-domruntime dependencies (also used in the production environment). - The second line
npm install -D @types/reactline, adds a development dependency ,that will not be used in the production environment. -Dspecify that the@types/reactpackage is a development dependency that won't be used at runtime (production).
TODO: Explain how to write tests, what naming convention and best practices
The frontend uses Vitest as its testing framework across all packages.
Resources:
TODO: Explain how to run tests
TODO: Explain how to generate the documentation