Skip to content

Latest commit

 

History

History
602 lines (423 loc) · 29.4 KB

File metadata and controls

602 lines (423 loc) · 29.4 KB

Contributing

In this document you will find a lot of information on how you can contribute to DukeOps.

How to contribute

Project Board

To get an overview of ongoing and upcoming work, you can check our project board, which organizes issues in a Kanban-style workflow:

Status Description
Backlog Ideas and requests that are not yet analyzed.
In Analysis Issues currently under discussion or refinement.
Ready Issues that are fully specified and ready to be implemented.
In Progress Issues actively being worked on.
Done Recently completed issues; older ones are archived over time.

Tip

If you’re looking for something to work on, focus on issues in the Ready column. These contain all necessary information to start contributing right away.

Good first issues

To find possible tasks for your first contribution to DukeOps, we tagged some of the hopefully easier to solve issues as good first issue.

If you prefer to meet people in real life to contribute to DukeOps together, we recommend to visit a Hackergarten event. DukeOps is often selected as a contribution target in Lucerne, Zurich, and at the CyberLand Open Source Camp.

Please join our developer community using our Matrix chat to get support and help for contributing to DukeOps.

Commit Messages

Please follow the Conventional Commits specification for all commit messages and pull request titles. This structured format helps automate changelogs, release processes, and code reviews.

For DukeOps, we love to add the issue number to the end of the commit message. This helps to track the changes in the code and the issue tracker. When the commit closes an issue, the closes, fixes, or resolves keyword can be used. This will automatically close the issue when the commit is merged into the main branch.

A commit message consists of the following parts:

<type>[optional scope]: <short description> [optional keyword] #<issue number>

[optional body]

[optional footer]

Examples

  • feat: add support for passkey authentication closes #123
  • fix(event): correct date formatting in export fixes #456
  • chore: add missing license headers to source files resolves #789

Common type values:

Type Purpose
feat Introduce a new feature
fix Fix a bug
docs Documentation-only changes
style Code style changes (formatting, whitespace, missing semicolons, etc.)
refactor Code refactoring without functional change
perf Improve performance without changing features or behavior
test Add or update tests
build Changes to the build system, packaging or dependencies (e.g. Maven)
ci Changes to CI/CD configuration (e.g. GitHub Actions, Woodpecker)
chore Maintenance tasks (configs, license, release meta, etc.)
revert Revert a previous commit
deps Add, update, or remove dependencies
security Address security issues or vulnerabilities

Tip

For more information, see conventionalcommits.org

Sign-off your commits

It is important to sign-off every commit. That is a de facto standard way to ensure that you have the right to submit your content and that you agree to the DCO (Developer Certificate of Origin).

You can find more information about why this is important and how to do it easily in a very good blog post by Josef Andersson.

AI generated code

AI generated source code is based on real existing source code, which is copied in whole or in part into the generated code. The license of the original source code with which the AI was trained is not taken into account. It is not clear which license conditions apply and how these can be complied with. For legal reasons, we therefore do not allow AI-generated source code at all.

Testing

Assertions

DukeOps uses AssertJ for writing assertions in unit tests. To ensure consistency, readability, and rich failure messages, only AssertJ should be used for all assertions. The use of JUnit's built-in assert* methods and Hamcrest matchers is explicitly disallowed and enforced through architecture tests. This includes, but is not limited to:

  • org.junit.Assert
  • org.junit.jupiter.api.Assertions
  • org.hamcrest.MatcherAssert

Instead, use AssertJ's fluent assertion style, for example:

assertThat(actual).isEqualTo(expected);

assertThat(collection).containsExactly("one", "two");

assertThatThrownBy(() -> service.doSomethingBad())
    .isInstanceOf(IllegalArgumentException.class);

Integration Tests

All integration tests must extend the abstract KaribuTest or BrowserTest base class. This ensures consistent configuration of the Spring Boot context, dependency injection, and database setup across all integration tests. Extend from the KaribuTest class for tests that do not require a browser using Karibu Testing, or from the BrowserTest class for tests that need to interact with the UI in a browser environment using Playwright.

Do not annotate integration tests manually with @SpringBootTest, @Transactional, or other Spring annotations. This is already handled by the base class. The base classes also provide shared utilities and guarantees a controlled test environment. To distinguish integration tests from unit tests, all Karibu test classes should be named with the KT suffix and all Playwright browser tests should be named with the BT suffix.

Example:

class UserServiceIT extends KaribuTest {

    @Autowired
    private UserService userService;

    @Test
    void shouldReturnUserByEmail() {
        final var user = userService.findByEmail("demo@example.eu");
        assertThat(user).isPresent();
    }
}

Warning

Integration tests that do not extend KaribuTest or BrowserTest may not run correctly or may produce unforeseen errors.

Pull Request Scope

Please make sure that each pull request is focused on a single issue or change. Avoid bundling unrelated modifications together in the same PR, as this makes the review process harder and slows down merging. If you want to contribute multiple independent changes, submit them as separate pull requests so they can be reviewed and merged individually.

Note

GitHub only allows one pull request per branch. To open multiple pull requests, you need to create a separate branch for each change.

Before Submitting a Pull Request

Please make sure your contribution meets all the following requirements:

  • Checkstyle and PMD report no violations.
  • The Modernizer Maven Plugin passes without issues.
  • All automated tests are green.
  • Test coverage is 100% (we practice Test-Driven Development).
  • License headers are present in all source files (missing headers can be added using: ./mvnw license:format).
  • The branch is up to date with the main branch (please rebase if necessary before opening a Pull Request).
  • The Pull Request title follows the Conventional Commit style and references the related issue number (e.g. feat: add event export closes #123).

Tip

All checks above are executed by:

./mvnw verify

Run this locally and ensure it completes successfully before opening a Pull Request.
The CI pipeline will run the same checks and block PRs that fail.

Commit & signature policy

  • Every commit must include a Signed-off-by line (Developer Certificate of Origin).
  • Every commit must be cryptographically signed with GPG, SSH, or S/MIME.
  • GitHub verifies these signatures during Pull Requests; unverified commits may be rejected.
  • For detailed setup and usage instructions, see Signing commits – GitHub Docs.

Quickstart for Local Development

This guide helps you get DukeOps up and running on your local machine for development purposes.

Prerequisites

Make sure the following tools are installed:

  • Git
  • Java LTS (currently version 25) - check with java -version
  • Podman or Docker

Important Podman Information

Linux Users

If you are using Podman on Linux, you need to enable the podman.socket systemd service. This allows non-root users to run Podman commands without sudo.

You can enable it with the following command:

systemctl --user enable --now podman.socket

This command starts the Podman socket for your user and ensures it automatically starts on login. Once enabled, you can use Podman just like Docker, without elevated privileges.

To make sure that Testcontainers can connect to your Podman socket, set the following environment variables in your shell:

export DOCKER_HOST=unix://$(podman machine inspect --format '{{.ConnectionInfo.PodmanSocket.Path}}')
export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=$(podman machine inspect --format '{{.ConnectionInfo.PodmanSocket.Path}}')
export TESTCONTAINERS_RYUK_CONTAINER_PRIVILEGED=true
macOS Users

If you use Podman Desktop for macOS, activate the full Docker compatibility layer instead:

  1. Start the "Podman Desktop" app.
  2. Navigate to "Settings", "Preferences", "Docker Compatibility" and enable it.
  3. Now a new entry "Docker Compatibility" appears directly under "Settings".
  4. Click on the new entry "Docker Compatibility".
  5. Make sure, "Third-Party Docker Tool Compatibility" is activated.

Clone and Verify

  1. Fork the DukeOps repository on GitHub.

  2. Clone your fork locally:

    git clone https://github.com/YOUR_USERNAME/dukeops.git
    cd dukeops
  3. Run all tests to verify that everything works:

    ./mvnw verify

Start Required Services

DukeOps needs a database and a mail server to run. For local development, you can start both using the provided docker-compose.yml file in the dev-tools directory:

Docker

cd ./dev-tools
docker compose up -d && docker compose logs -f || docker compose down

Podman

cd ./dev-tools
podman compose up -d && podman compose logs -f || podman compose down

The commands shown above will start the services in detached mode and then show the logs. If you stop the log view (e.g. using CTRL+C), all services will be stopped as well.

The following services will be available:

  • MariaDB on Port 3306 with persistent data in mariadb-data.
    The database user will be dukeops with the password dukeops. The database name is also dukeops.
  • phpMyAdmin (database web UI) on port 4000
  • Mailpit (SMTP server) on port 1025, web UI on port 8025, persistent data in mailpit-data

Important

This setup is for development only and not suitable for production use.

MariaDB Login Information

Field Value
System MySQL / MariaDB
Server mariadb
User dukeops
Password dukeops
Database dukeops

After starting the script, logs will be shown. To stop the services, press Ctrl+C. This stops all containers as well.

Configure Environment Variables

DukeOps is configured using environment variables. At minimum, the following must be set:

DUKEOPS_INSTANCE_ADMIN=admin@example.com
DUKEOPS_DB_URL=jdbc:mariadb://localhost:3306/dukeops?serverTimezone=Europe/Zurich&allowMultiQueries=true
DUKEOPS_DB_USER=dukeops
DUKEOPS_DB_PASS=dukeops
DUKEOPS_MAIL_HOST=localhost
DUKEOPS_MAIL_PORT=1025

If you're in a different timezone, adjust the serverTimezone parameter accordingly. You can define these variables via your IDE, or directly in your shell.

Run DukeOps

You can run DukeOps either via your IDE or the command line. On the command line, use the Maven Wrapper:

./mvnw

This runs the eu.ijug.dukeops.Application main class.

Open in Browser

Once the application is running, open your browser and navigate to:

http://localhost:8080/

Make sure JavaScript is enabled.

Stop DukeOps

  • In your IDE: use the stop button
  • In the terminal: press Ctrl+C

Communication

Matrix Chat

There is a channel at Matrix for quick and easy communication. This is publicly accessible for everyone. For developers as well as users. The communication in this chat is to be regarded as short-lived and has no documentary character.

You can find our Matrix channel here: @dukeops:ijug.eu

GitHub Discussions

We use the corresponding GitHub function for discussions. The discussions held here are long-lived and divided into categories for the sake of clarity. One important category, for example, is that for questions and answers.

Discussions on GitHub: https://github.com/ijug-ev/dukeops/discussions
Questions and Answers: https://github.com/ijug-ev/dukeops/discussions/categories/q-a

Architecture

The server of DukeOps is written using the Java programming language. The main framework is Spring. For the user interface, we use Vaadin Flow. To access the database, we rely on jOOQ. To coordinate the build process, Maven is used.

Structure

Vaadin web applications are full-stack and include both client-side and server-side code in the same project.

Directory Description
src/main/frontend/themes/dukeops/ Base directory for default DukeOps theme
    components/ Directory with CSS files for UI components
    fonts/ Directory containing font files
    views/ Directory with CSS files for UI views
    styles.css Base CSS file with includes only
    theme.json Theme configuration for Vaadin
src/main/java/eu/ijug/dukeops/ Server-side source directory
    domain/ Code for all business domains (BCE architecture)
    jooq/ jOOQ DB layer code (converters, generators, etc.)
    util/ Universal utility classes (not bound to a business domain)
    vaadin/components/ Universal UI components (not bound to a business domain)
    Application.java Server entrypoint with main class

Boundary Control Entity Architecture

DukeOps follows the Boundary Control Entity (BCE) architecture pattern. This pattern divides the application into three main layers:

  • Boundary Layer: This layer contains all user interface components, including Vaadin views and UI components. It handles user interactions and displays data to the user.
  • Control Layer: This layer contains the service classes that implement the business logic of the application. It acts as an intermediary between the boundary and entity layers, coordinating data flow and enforcing business rules.
  • Entity Layer: This layer contains the data model, like DTOs.

Inside the domain package, each business domain has its own subpackage containing its boundary, control, and entity classes. This modular structure promotes separation of concerns, making the codebase easier to maintain and extend. Each layer has a well-defined responsibility, ensuring that changes in one layer have minimal impact on others. Example:

Directory Description
src/main/java/eu/ijug/dukeops/domain/ Code for all business domains (BCE architecture)
    community/ Subpackage for the "Community" domain
    event/ Subpackage for the "Event" domain
    core/ Subpackage for core features not specific to a domain
    core/config Subpackage for the "Configuration" core feature

Inside each domain subpackage, you will find the following structure:

Directory Description
src/main/java/eu/ijug/dukeops/domain/community/ Subpackage for the "Community" domain
    boundary/ Everything regarding the Boundary Layer
    control/ Everything regarding the Control Layer
    entity Everything regarding the Entity Layer

Tip

More information about the BCE architecture can be found on the BDE Design website.

Useful Vaadin Links

Internationalization

Important

DukeOps uses the ICU message format for all translation files. This allows for advanced formatting options, including pluralization and named parameters. ICU stands for "International Components for Unicode".

Translation Files

All user-facing text is externalized into properties files located in the src/main/resources/vaadin-i18n/ directory. The english language is the default and is stored in translations.properties. Other languages follow the naming convention translations_xx.properties, where xx is the ISO 639-1 language code (e.g., de for German, fr for French).

As the key for the translations, we use a dot-separated hierarchical structure that reflects the package structure without the leading eu.ijug.dukeops and include the class name. This makes it easier to locate and manage translations.

Please try keep the translation files sorted alphabetically by key for better maintainability.

Message Format

Important

The message needs to be added to the properties files on a single line without line breaks. Line breaks in the examples below are for better readability only!

Example (English):

community.boundary.CommunityDetailView.memberCount = {0, plural,
    =0 {no members}
    one {one member}
    other {# members}
}

Example (German):

community.boundary.CommunityDetailView.memberCount = {0, plural,
    =0 {keine Mitglieder}
    one {ein Mitglied}
    other {# Mitglieder}
}

Example (Russian):

community.boundary.CommunityDetailView.memberCount = {0, plural,
    =0 {нет участников}
    one {один участник}
    few {# участника}
    many {# участников}
    other {# участников}
}

Named Parameters

The ICU message format also supports named parameters for better readability.

Example (English):

community.boundary.CommunityDetailView.memberCount = {count, plural,
    =0 {no members}
    one {one member}
    other {{count} members}
}

Database

General Principles

  • Database independence: All schema definitions must be compatible with common SQL databases. Avoid vendor-specific features unless absolutely necessary.
  • Schema migrations: Use Flyway for schema versioning. Each migration must have a clearly written description and follow our naming convention.
  • Identifier generation: Always use application-generated IDs (e.g., UUIDs) instead of AUTO_INCREMENT, SERIAL, or database sequences to ensure cross-database compatibility.
  • Primary key constraints: Primary key constraints should not have explicit names; let the database assign a default name to avoid conflicts across different environments.
  • Timestamps: Use TIMESTAMP instead of DATETIME to ensure consistent handling of time zones and better cross-database support.
  • Enum usage: Use Java enum types mapped via jOOQ converters to VARCHAR columns in the database. Do not reference enum values from lookup tables.
  • DTO mapping: All database access must go through the service layer. Records returned by jOOQ must be converted to DTOs in a dedicated mapping layer.
  • Referential Integrity: Use foreign key constraints to ensure referential integrity where appropriate.
  • Optional Fields: If a field is optional, make sure the database column is nullable and check for null explicitly in the application logic.

Index Names

List of prefixes for index names:

Prefix Used For Example
uk_ Unique Key uk_group_profile
fk_ Foreign Key fk_event_group
idx_ Normal Index idx_event_begin

Primary keys don't have a name and therefore no prefix!

Starting a Local MariaDB Instance

To start a local MariaDB instance using Docker or Podman, you can use the provided docker-compose.yml file in the dev-tools directory. Please see section Start Required Services for detailed instructions.

Build

Maven

DukeOps uses Maven to build the project. Please use standard Maven commands to build what you need:

Command What it does
./mvnw compile and run the app
./mvnw clean cleanup generated files and build artefacts
./mvnw compile compile the code without running the tests
./mvnw test compile and run all tests
./mvnw package compile, test, and create a JAR file to run it with Java directly
./mvnw verify compile, test, package, and run analysis tools

There is no need to run the install or deploy tasks. They will just run longer, produce unnecessary output, burn energy, and occupy your disk space. Don't just blindly run mvn clean install...

Running and debugging

There are two ways to run the application: From the command line or directly from your IDE.

Running the server from the command line.

To run from the command line, use ./mvnw and open http://localhost:8080 in your browser.

Running and debugging the server in Intellij IDEA

Using Maven

  • On the right side of the window, select "Maven" --> "Plugins" --> spring-boot --> spring-boot:run
  • Optionally, you can disable tests by clicking on a Skip Tests mode blue button.

After the server has started, you can view it at http://localhost:8080/ in your browser. You can now also attach breakpoints in code for debugging purposes, by clicking next to a line number in any source file.

Using the main method

  • Locate the Application.java class in the Project view. It is in the src folder, under the main package's root.
  • Right-click on the Application class
  • Select "Debug 'Application.main()'" from the list

After the server has started, you can view it at http://localhost:8080/ in your browser. You can now also attach breakpoints in code for debugging purposes, by clicking next to a line number in any source file.

Running and debugging the server in Eclipse

Using Maven

  • Right click on a project folder and select Run As --> Maven build.... After that a configuration window is opened.
  • In the window set the value of the Goals field to spring-boot:run
  • You can optionally select the Skip tests checkbox
  • All the other settings can be left to default

Once configurations are set clicking Run will start the application.

Do not worry if the debugger breaks at a SilentExitException. This is a Spring Boot feature and happens on every startup.

After the server has started, you can view it at http://localhost:8080/ in your browser. You can now also attach breakpoints in code for debugging purposes, by clicking next to a line number in any source file.

Using the main method

  • Locate the Application.java class in the Package Explorer. It is in src/main/java, under the main package.
  • Right-click on the file and select Debug As --> Java Application.

Do not worry if the debugger breaks at a SilentExitException. This is a Spring Boot feature and happens on every startup.

After the server has started, you can view it at http://localhost:8080/ in your browser. You can now also attach breakpoints in code for debugging purposes, by clicking next to a line number in any source file.

Running using Docker

Tip

There is an official Docker image you can use to run DukeOps.

To build the dockerized version of DukeOps yourself, run:

docker build . -t dukeops:latest

Once the Docker image is correctly built, you can test it locally using:

docker run -p 8080:8080 dukeops:latest