Skip to content

ISEP-Projects-JH/Todo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

45 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

TodoApp

A full-stack Todo application built with Spring Boot and Vue 3, demonstrating robust software engineering practices including Hexagonal Architecture, BDD/TDD, and dual-track CI/CD pipelines.

CI Coverage Jenkins

License: MIT Top Language Languages

Project Structure

The project follows a clean separation of concerns:

  • frontend/: Vue 3 application (Vite, TypeScript, Pinia).
  • src/main/java/: Spring Boot backend following Hexagonal Architecture.
    • domain/: Pure business logic and domain models (e.g., Todo, Tag).
    • repository/: Storage interfaces and implementations (memory vs persistence).
    • service/: Business use cases (TodoService).
    • controller/: REST adapters.
    • config/: Spring configuration classes.
  • src/test/resources/features/: Cucumber Gherkin feature files (in-memory, database, restful).
  • scripts/: Python automation scripts for E2E testing (auto_test.py).
  • vendor/: Third-party dependencies (e.g., jh_utils).
Todo/
β”œβ”€β”€ .github/
β”‚   └── workflows/
β”‚       β”œβ”€β”€ ci.yml              # Backend CI: Maven + Cucumber + PostgreSQL
β”‚       └── run_jenkins.yml     # Frontend CI: Dockerized Jenkins + Selenium
β”œβ”€β”€ frontend/
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ api/                # API client (Axios)
β”‚   β”‚   β”œβ”€β”€ router/             # Vue Router configuration
β”‚   β”‚   β”œβ”€β”€ stores/             # Pinia state management
β”‚   β”‚   β”œβ”€β”€ views/              # Vue pages (Dashboard, TodoList)
β”‚   β”‚   β”œβ”€β”€ App.vue             # Root component
β”‚   β”‚   └── main.ts             # Entry point
β”‚   └── vite.config.ts          # Vite config (Proxy to backend port 8000)
β”œβ”€β”€ scripts/
β”‚   └── auto_test.py            # E2E Test Script (Selenium + jh_utils)
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ main/
β”‚   β”‚   β”œβ”€β”€ java/com/todoapp/
β”‚   β”‚   β”‚   β”œβ”€β”€ config/         # Spring Configuration (Memory/DB profiles)
β”‚   β”‚   β”‚   β”œβ”€β”€ controller/     # REST Controllers & DTOs
β”‚   β”‚   β”‚   β”œβ”€β”€ domain/         # Core Domain Models (Hexagonal Core)
β”‚   β”‚   β”‚   β”œβ”€β”€ repository/     # Repository Interfaces & Implementations
β”‚   β”‚   β”‚   β”œβ”€β”€ service/        # Business Logic Services
β”‚   β”‚   β”‚   └── TodoAppApplication.java
β”‚   β”‚   └── resources/
β”‚   β”‚       β”œβ”€β”€ application.properties # Main configuration
β”‚   β”‚       └── schema.sql      # Database schema
β”‚   └── test/
β”‚       β”œβ”€β”€ java/com/todoapp/   # Test Steps & Runner
β”‚       └── resources/features/ # Cucumber Feature Files (Gherkin)
β”œβ”€β”€ vendor/
β”‚   └── jh_utils/               # Third-party utility library (Submodule)
β”œβ”€β”€ Dockerfile                  # Jenkins agent image definition
β”œβ”€β”€ Jenkinsfile                 # Jenkins pipeline definition
β”œβ”€β”€ job.xml                     # Jenkins job configuration template
β”œβ”€β”€ pom.xml                     # Maven build configuration
└── README.md                   # Project documentation

Tech Stack

Frontend

  • Framework: Vue 3 (Composition API)
  • Language: TypeScript
  • Build Tool: Vite
  • State Management: Pinia
  • Styling: Modern CSS
  • Configuration: The frontend is currently hardcoded to communicate with the backend on port 8000, matching the default backend configuration.

Backend

  • Framework: Spring Boot 3
  • Language: Java 21
  • Persistence: Spring Data JDBC
    • In-Memory: ConcurrentHashMap based implementation for fast testing.
    • Database: PostgreSQL for production/persistence.
  • Architecture: Hexagonal Architecture (Ports and Adapters), ensuring the domain logic remains independent of frameworks and databases.

UML Diagram of the backend

                                                              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                                              β”‚TodoService                                                                                                 β”‚
                                                              β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
                                                              β”‚Todo createTodo(String title, String description, java.util.Set<String> tagNames)                           β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                           β”‚Todo updateTodoByTitle(String oldTitle, String newTitle, String description, java.util.Set<String> tagNames)β”‚
β”‚Todo                             β”‚                           β”‚void deleteByTitle(String title)                                                                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€                           β”‚void clearAll()                                                                                             β”‚
β”‚Long id                          β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚Todo markCompletedByTitle(String title)                                                                     β”‚
β”‚String title                     β”‚  β”‚DashboardService    β”‚   β”‚Todo markPendingByTitle(String title)                                                                       β”‚
β”‚String description               β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€   β”‚java.util.List<Todo> listTodos()                                                                            β”‚
β”‚boolean completed                β”‚  β”‚int completedCount()β”‚   β”‚java.util.List<Todo> searchTodos(String query)                                                              β”‚
β”‚java.time.LocalDateTime createdAtβ”‚  β”‚int pendingCount()  β”‚   β”‚java.util.List<Todo> listTodosBefore(java.time.LocalDateTime before)                                        β”‚
β”‚java.time.LocalDateTime updatedAtβ”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚java.util.List<Todo> listTodosBefore(java.time.LocalDateTime before, int limit)                             β”‚
β”‚java.util.Set<Tag> tags          β”‚                           β”‚java.util.List<Todo> findByTag(String tag)                                                                  β”‚
β”‚void markCompleted()             β”‚                           β”‚Todo addTags(String title, java.util.Set<String> tagNames)                                                  β”‚
β”‚void markPending()               β”‚                           β”‚Todo removeTags(String title, java.util.Set<String> tagNames)                                               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                           β”‚int countCompleted()                                                                                        β”‚
                 |                                            β”‚int countPending()                                                                                          β”‚
                 |                                            β”‚Todo getByTitle(String title)                                                                               β”‚
                 |                                            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 |                                                                                                   |                                                      
                 |                                                                                                   |                                                      
                 |          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                          |                                                      
                 |          β”‚TodoRepository                                               β”‚                          |                                                      
                 |          β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                 
           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚Todo save(Todo)                                              β”‚    β”‚TagRepository                             β”‚                                 
           β”‚Tag        β”‚    β”‚java.util.Optional<Todo> findById(Long)                      β”‚    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€                                 
           β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€    β”‚java.util.Optional<Todo> findByTitle(String)                 β”‚    β”‚Tag save(Tag)                             β”‚                                 
           β”‚Long id    β”‚    β”‚java.util.List<Todo> findAll()                               β”‚    β”‚java.util.Optional<Tag> findById(Long)    β”‚                                 
           β”‚String nameβ”‚    β”‚void deleteById(Long)                                        β”‚    β”‚java.util.Optional<Tag> findByName(String)β”‚                                 
           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚java.util.List<Todo> searchByText(String)                    β”‚    β”‚java.util.List<Tag> findAll()             β”‚                                 
                            β”‚java.util.List<Todo> findBefore(java.time.LocalDateTime, int)β”‚    β”‚void deleteAll()                          β”‚                                 
                            β”‚java.util.List<Todo> findByTag(String)                       β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                 
                            β”‚void deleteAll()                                             β”‚                                                                                 
                            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                                                                 

View the complete PlantUML diagram (requires a PlantUML viewer).

Integration & CI/CD

The project utilizes a dual-track CI/CD strategy to balance speed and reliability:

  1. Backend CI (.github/workflows/ci.yml):

    • Tool: GitHub Actions.
    • Scope: Validates backend business logic and persistence.
    • Stack: Maven + Cucumber.
    • Profile: Runs with a real PostgreSQL database to ensure data integrity and SQL correctness.
  2. Frontend/E2E CI (.github/workflows/run_jenkins.yml):

    • Tool: Jenkins (running in Docker).
    • Scope: Validates the full application stack (Frontend + Backend) and UI interactions.
    • Stack: Jenkins Pipeline (Jenkinsfile) + Selenium (scripts/auto_test.py).
    • Profile: Runs the backend in In-Memory mode (-Dtodo.repository.type=memory) for lightweight, high-speed execution without database overhead.

Manual Testing

In addition to automated tests, a comprehensive manual testing campaign was conducted to ensure usability and functional correctness from an end-user perspective.

The following documents provide a complete overview of the manual testing process:

Configuration

The application uses Spring Profiles to manage different environments:

  • Production: Intended to run as a compiled JAR.
  • Test Support: The Maven environment fully supports the test profile.
    • Specific interfaces or configurations not meant for production (like the In-Memory repository or test-specific security configs) are strictly guarded by @Profile("test") to prevent exposure in production environments.
  • Properties: Configuration is managed via application.properties and programmatic Config classes in src/main/java/com/todoapp/config/.

Development Methodology: BDD & TDD

This project was developed using Behavior-Driven Development (BDD) and Test-Driven Development (TDD), leveraging Cucumber as the foundation.

  • Three Semantic Interfaces:
    1. In-Memory: For rapid domain logic verification.
    2. Database: For persistence layer verification.
    3. Restful: For API contract verification.
  • Hexagonal Architecture: The architecture supports swapping these adapters (memory vs database) seamlessly without changing the core domain logic.
  • Agenic Coding: The BDD/TDD approach facilitated fast, AI-assisted agentic coding by providing clear executable specifications.
  • Refactoring: A distinct "Blue" (Refactor) phase was employed to optimize code structure and quality after getting tests to pass.

Authors

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

A full-stack Todo application built with Spring Boot and Vue 3, demonstrating robust software engineering practices including Hexagonal Architecture, BDD/TDD, and dual-track CI/CD pipelines.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors