Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 16 additions & 23 deletions .github/workflows/licence-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,24 @@ on:
- '*'

jobs:
licence-header-check:
spotless-check:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Check Java licence headers
run: |
header1="/*"
header2=" * Copyright (C) 2026 Frachtwerk GmbH, Leopoldstraße 7C, 76133 Karlsruhe."
fail=0
while IFS= read -r -d '' file; do
line1=$(head -n 1 "$file" | tr -d '\r')
line2=$(head -n 2 "$file" | tail -n 1 | tr -d '\r')
if [[ "$line1" != "$header1" || "$line2" != "$header2" ]]; then
echo "::error file=$file::Licence header missing or incorrect."
echo "Found:"
echo "$line1"
echo "$line2"
fail=1
fi
done < <(find . -type d -name target -prune -o -type f -name "*.java" -print0)
if [[ $fail -eq 1 ]]; then
exit 1
fi
- uses: actions/checkout@v6
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '21'
cache: 'maven'
- name: Spotless check (essencium-backend)
run: mvn -B -ntp spotless:check
working-directory: essencium-backend
- name: Spotless check (essencium-backend-development)
run: mvn -B -ntp spotless:check
working-directory: essencium-backend-development
- name: Spotless check (essencium-backend-development-uuid)
run: mvn -B -ntp spotless:check
working-directory: essencium-backend-development-uuid

yml-licence-header-check:
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
- upgraded org.wiremock.integrations:wiremock-spring-boot from 4.0.8 to 4.2.1
- upgraded com.fasterxml.jackson:jackson-bom from 2.21.1 to 2.21.2
- removed spring-boot-properties-migrator from dependencies
- replaced `com.cosium.code:git-code-format-maven-plugin` with `com.diffplug.spotless:spotless-maven-plugin` 3.4.0 across all modules. Spotless enforces Google Java Format, no wildcard imports, import ordering, and the LGPL-3.0 license header. `mvn spotless:apply` formats; `mvn verify` (and the dedicated CI job) fails on violations. No git pre-commit hook is required.

## Version `3.3.2` HOTFIX

Expand Down
39 changes: 39 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,45 @@

## Version `3.4.0`

### Build: switched from `git-code-format-maven-plugin` to `spotless-maven-plugin`

The change is internal to essencium-backend's own build. **Downstream projects
that consume essencium-backend as a Maven dependency are not affected** — your
own code does not need the LGPL header, and you do not have to switch
formatters. You may keep using whatever formatter you prefer in your project.

If you maintain a downstream project that copied the **formatter configuration**
from this repository (or one of the demo modules) verbatim, update your pom:

- Remove the `<plugin>` block for `com.cosium.code:git-code-format-maven-plugin`
and its `install-formatter-hook` / `validate-code-format` executions.
- If you want Spotless, add the `<plugin>` block for
`com.diffplug.spotless:spotless-maven-plugin` (the configuration in
`essencium-backend/pom.xml` is a starting point). The `<licenseHeader>` rule
in our config enforces the LGPL-3.0 block on essencium-backend's own
sources — your project does **not** need any license header on your code.
You may either drop the `<licenseHeader>` block entirely, or keep one
pointing at your own header file (LGPL or otherwise) if you want Spotless
to enforce a project-specific notice.
- Replace any `mvn git-code-format:format-code` invocations (CI scripts,
Makefiles, IDE run configs) with `mvn spotless:apply`.
- Replace `mvn git-code-format:validate-code-format` with `mvn spotless:check`
(or rely on `mvn verify`, where `spotless:check` is bound by default).
- The git pre-commit hook installed by the old plugin is no longer needed —
formatting is enforced by `mvn verify` and a dedicated CI job. If you ever
ran `mvn git-code-format:install-hooks` locally, delete the leftover hook
files; otherwise every commit will fail with
`No interface com.cosium.code.format_spi.CodeFormatter instance found`:
```bash
rm .git/hooks/pre-commit .git/hooks/essencium-backend.git-code-format.pre-commit.sh
```

The Spotless rules enabled in this repository are: `googleJavaFormat`,
`removeUnusedImports`, `forbidWildcardImports`, and `importOrder`, plus the
LGPL `licenseHeader` (which only applies to essencium-backend's own sources).
`forbidWildcardImports` fails the build on any `import …*;` line — expand
wildcards to explicit imports if your code currently uses them.

### Access-control change: `terminate` endpoint honours ownership specs

`POST /v1/users/{id}/terminate` now runs `testAccess(spec)` before fetching the
Expand Down
3 changes: 2 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ To run the documentation locally, run:
cd <repo>/docs
pnpm install
pnpm dev
visit http://localhost:3000
```

Then open **http://localhost:3000/essencium-backend** in your browser. The `/essencium-backend` suffix is required because of `basePath` in `next.config.mjs`.

## Building

To build the documentation for production:
Expand Down
20 changes: 20 additions & 0 deletions docs/pages/styleguide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,26 @@ This section contains the coding standards and conventions for the Essencium Bac
title="Coding Conventions"
href="/styleguide/styling-conventions"
/>
<Cards.Card
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3.75 12h16.5m-16.5 3.75h16.5M3.75 19.5h16.5M5.625 4.5h12.75a1.875 1.875 0 010 3.75H5.625a1.875 1.875 0 010-3.75z"
/>
</svg>
}
title="OpenAPI Annotations"
href="/styleguide/openapi"
/>
</Cards>

## Purpose
Expand Down
1 change: 1 addition & 0 deletions docs/pages/styleguide/_meta.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export default {
overview: 'Overview',
'styling-conventions': 'Coding Conventions',
openapi: 'OpenAPI Annotations',
}
194 changes: 194 additions & 0 deletions docs/pages/styleguide/openapi.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { Callout } from 'nextra/components'

# OpenAPI Annotations

Essencium-backend exposes its REST API surface to Swagger UI / ReDoc via [springdoc-openapi](https://springdoc.org/). The conventions on this page reflect what existing controllers in `de.frachtwerk.essencium.backend.controller` already do — keep new endpoints consistent with them.

<Callout type="info">
Springdoc derives most schema information from the method signature, return
type, and Bean Validation annotations. Reach for an OpenAPI annotation only
when the framework cannot infer what you need — duplicating information that
springdoc already produces is the most common source of drift.
</Callout>

## How the pieces fit together

```
@RestController springdoc-openapi /v3/api-docs (JSON)
─ method signatures reads annotations ──► served at runtime
─ DTOs / return types ──► + reflection │
─ @Tag, @Operation, ▼
@Parameter, @Schema Swagger UI / ReDoc
```

Write the controller as you normally would, then add OpenAPI annotations only to fill gaps the framework cannot see (the intent of an endpoint, what a query parameter means, which formats are accepted).

## Quick reference

| You want to… | Use | Where it goes |
| ---------------------------------------------------------------- | --------------------------------- | ------------------------- |
| Group related endpoints under one heading in Swagger UI | `@Tag` | Class |
| Document what a single endpoint does | `@Operation` | Method |
| Hide an injected argument (e.g. `@AuthenticationPrincipal`) | `@Parameter(hidden = true)` | Method argument |
| Document a `Pageable` endpoint's `page` / `size` / `sort` params | `@Parameter` + `@ParameterObject` | Method (annotation) + arg |
| Add a format / example to a primitive query parameter | `@Parameter` with `@Schema` | Method |
| Override the schema for a specific DTO field | `@Schema` | DTO field |
| Document a non-default response (e.g. a 404 case worth listing) | `@ApiResponse` | Method |

## Class-level: `@Tag`

Group related endpoints under a `@Tag` so Swagger UI renders them in their own section. Every controller gets exactly one tag, named after the controller class.

```java
@Tag(
name = "RightController",
description = "Set of endpoints to manage global application rights / permissions")
public class RightController {
// ...
}
```

For very small controllers, the inline form is fine:

```java
@Tag(name = "ContactController", description = "Set of endpoints to send arbitrary emails")
public class ContactController<ID extends Serializable> {
// ...
}
```

- `name` matches the controller's simple class name. This keeps the tag stable when the URL prefix changes.
- `description` is a single sentence explaining the resource family — not implementation details.

## Method-level: `@Operation`

Every public endpoint method carries `@Operation`. Two styles are in use.

**Single-line `description`** for self-evident endpoints:

```java
@PostMapping
@Operation(description = "As a logged in user, send a certain message to the given address")
public void sendContactRequest(/* ... */) { /* ... */ }
```

**`summary` + `description`** for endpoints whose intent is not obvious from the URL:

```java
@PostMapping("/token")
@Operation(
summary = "Login with username and password",
description = "Log in to request a new JWT token using username and password")
public TokenResponse postLogin(/* ... */) { /* ... */ }
```

- `summary` is a short verb phrase ("Login with username and password"), used as the heading in Swagger UI.
- `description` is a full sentence and may include constraints, side effects, or auth requirements.

## Parameter-level: `@Parameter`

`@Parameter` is reserved for cases where springdoc cannot derive the binding automatically.

### Hide injected principals

Authenticated user details bound via `@AuthenticationPrincipal` must not appear in the OpenAPI document.

```java
public void sendContactRequest(
@RequestBody @NotNull @Valid final ContactRequestDto contactRequest,
@Parameter(hidden = true) @AuthenticationPrincipal final EssenciumUserDetails<ID> user) {
// ...
}
```

### Document `Pageable` query parameters

Springdoc cannot introspect Spring's `Pageable` directly. Use `@Parameter` at the method level with `in = ParameterIn.QUERY`:

```java
@GetMapping
@Parameter(
in = ParameterIn.QUERY,
description = "Page you want to retrieve (0..N)",
name = "page",
schema = @Schema(type = "integer", defaultValue = "0"))
@Parameter(
in = ParameterIn.QUERY,
description = "Number of records per page.",
name = "size",
schema = @Schema(type = "integer", defaultValue = "20"))
@Parameter(
in = ParameterIn.QUERY,
description =
"Sorting criteria in the format: property(,)(asc|desc). "
+ "Default sort order is ascending. "
+ "Multiple sort criteria are supported.",
name = "sort",
array = @ArraySchema(schema = @Schema(type = "string")))
@Operation(description = "List all available rights")
public Page<Right> findAll(@NotNull @ParameterObject final Pageable pageable) {
return rightService.getAll(pageable);
}
```

Use `@ParameterObject` (springdoc) on the `Pageable` parameter so the binding itself is not surfaced — the `@Parameter` annotations above describe the resulting query keys.

### Constrain a single primitive query parameter

When a primitive needs schema metadata (format, example) that the parameter type does not convey:

```java
@Parameter(
in = ParameterIn.QUERY,
name = "redirectUrl",
description = "URL to redirect to after logout",
schema = @Schema(type = "string", format = "uri", example = "https://example.com/logout"))
@Operation(summary = "Logout the currently logged-in user")
public void logout(/* ... */) { /* ... */ }
```

For arrays/lists in query strings, use `array = @ArraySchema(schema = @Schema(...))`, not `schema = @Schema(type = "array")`.

## Schemas

`@Schema` is rarely needed at the type level — Jackson plus Bean Validation already produce a usable schema. Reach for it when:

- A type has a non-obvious accepted format (`format = "uri"`, `format = "date-time"`, `format = "email"`).
- An endpoint accepts a primitive whose example is genuinely useful (`example = "https://example.com/logout"`).

Prefer Bean Validation annotations (`@NotNull`, `@Valid`, `@Size`, …) over duplicating the same constraint inside `@Schema`.

## Annotation order

The annotation order in the existing codebase is:

1. Spring HTTP mapping (`@GetMapping`, `@PostMapping`, …)
2. `@Parameter` blocks (one per documented query parameter)
3. `@Secured` / `@PreAuthorize` (authorization)
4. `@Operation` (description / summary)
5. Method signature

```java
@GetMapping
@Parameter(/* ... page ... */)
@Parameter(/* ... size ... */)
@Parameter(/* ... sort ... */)
@Secured({BasicApplicationRight.Authority.RIGHT_READ})
@Operation(description = "List all available rights")
public Page<Right> findAll(@NotNull @ParameterObject final Pageable pageable) { /* ... */ }
```

Do not interleave `@Operation` between `@Parameter` blocks — keep them grouped.

## What to avoid

<Callout type="warning">
Do **not** define controller-wide `@ApiResponses` with hand-written JSON
examples. The runtime response type is the source of truth; copies drift
immediately and silently.
</Callout>

- Do not annotate framework types (`HttpServletRequest`, `HttpServletResponse`, `@AuthenticationPrincipal`, JWT/refresh-token cookies) so they appear in the spec — hide them with `@Parameter(hidden = true)` or rely on the framework binding.
- Do not add a redundant `summary` when the `description` is already a short sentence; pick one.
- Do not embed response-body example payloads in `@Schema` blocks. Examples copied verbatim into annotations rot quickly; let springdoc generate them from the actual DTO classes.
- Do not override the controller's tag inside individual operations.
13 changes: 8 additions & 5 deletions docs/pages/styleguide/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,15 @@ The Essencium Backend follows standard Java conventions and Spring Boot best pra

## Lombok Usage

We use Lombok extensively to reduce boilerplate:
Lombok is used extensively across model, DTO, representation, and configuration classes to reduce boilerplate. The conventions in use:

- `@Data` for simple POJOs
- `@Builder` for immutable objects
- `@Slf4j` for logging
- Avoid overusing `@Data` on entities (use specific annotations instead)
- `@Data` is used on entities, DTOs, and configuration property classes (`Role`, `Right`, `AbstractBaseModel`, `BaseUserDto`, `AppCorsProperties`, …). This is the project's default for value-style classes.
- `@SuperBuilder` (not plain `@Builder`) on classes that participate in inheritance — e.g. anything extending `AbstractBaseModel` or `AbstractBaseUser`.
- `@NoArgsConstructor` + `@AllArgsConstructor` paired with `@Data` / `@SuperBuilder` so JPA, Jackson, and the builder all have the constructors they need.
- `@RequiredArgsConstructor` on services and controllers, with `private final` fields — the project favours constructor injection.
- `@Slf4j` for logging instead of declaring a `Logger` field.

Two existing entities (`Translation`, `ApiToken`) use individual `@Getter` / `@Setter` instead of `@Data` because their equality/hashCode behaviour needs to be hand-written. Reach for the same pattern only if `@Data`'s default `equals`/`hashCode` would be wrong for your class — otherwise stay on `@Data`.

## Spring Boot Conventions

Expand Down
Loading