Skip to content
HBTGmbHPublic

About

Time tracking, billing and budget controlling for consulting work — a Spring Boot modular monolith.

Topics

Resources

Stars

7 stars

Watchers

6 watching

Forks

Repository files navigation

New here?

Onboarding runs as a dialogue with the AI agent, not as a reading assignment: run /onboarding and it walks you through domain, roles, architecture, process and a first ticket, skipping what you already know. The process itself — and a lookup map of the core concepts — is described in docs/onboarding.md.

Run locally

Requirements:

  • Java 25
  • Docker (running)
  • docker-compose or docker compose

Steps to start Salat locally:

  1. Build the image: ./mvnw spring-boot:build-image
  2. Run docker-compose: docker-compose up -d (in newer docker versions use docker compose up -d)
  3. That's it. Salat should now be running. To check, open in browser: http://localhost:8080?login-name=tt

Shutdown:

  1. Stop docker-compose: CTRL+C
  2. Stop built containers: docker-compose stop (in newer docker versions use docker compose stop)
  3. If you want to remove the containers: docker-compose down (in newer docker versions use docker compose down)

Login

Open the URL <http://localhost:8080?login-name=>

You can change the login-name parameter to login as a different user. It is even possible to append ?login-name=<sign> to any URL, to log in the user.

Valid login-names in the test-dataset are:

  1. admin: Administrator
  2. bm: "Bossy Bossmann", Administrator
  3. tt: "Testy Testmann", Employee

Logout

There is no need to logout. But you can just click the button to logout.

With the above login url, you can change the login user at any time without logging out!

Debugging

To start only the local database, without the Salat application: docker-compose -f docker-compose-infra.yml up

⚠️ Be sure to remove existing docker containers before by running docker-compose down if you had the application running before.

Measuring response times locally (local-qa profile)

The local profile runs on the base configuration, which differs from production in exactly the settings that determine response time (asset caching, Thymeleaf template cache, the authorization rule cache expiry). Response times measured under local are therefore not transferable to production.

Use the local-qa profile instead. It keeps the local dev login and the local datasource, and overrides only the performance-relevant settings with their production values (see ADR-0020):

./mvnw spring-boot:run -Dspring-boot.run.profiles=local-qa \
  -Dspring-boot.run.jvmArguments="-Dspring.devtools.restart.enabled=false"

spring.devtools.restart.enabled has to be passed as a JVM argument — it is evaluated before the configuration files are read, so setting it in a profile has no effect. All other devtools property defaults are switched off by the profile itself.

Per-endpoint timings are then available at

http://localhost:8080/actuator/metrics/http.server.requests?tag=uri:/dailyreport/dashboard&login-name=<sign>

The endpoint sits behind the authenticated filter chain, so it needs a valid login-name (or the salat_dev_login cookie) just like any page. It reports COUNT, TOTAL_TIME and MAX; the mean is TOTAL_TIME / COUNT. Percentiles are configured as histogram buckets and become readable once a scraping registry (e.g. Prometheus) is added — the /actuator/metrics endpoint itself does not render them.

Two things outside the application profile, which matter a lot when you have imported a production data dump into your local MySQL:

  • InnoDB buffer pool. Production runs innodb_buffer_pool_size=536870912 (512 MB). The db service in docker-compose-infra.yml and docker-compose.yml starts the testdb container with the same value (--innodb-buffer-pool-size=512M); the mysql:8 default of 128 MB alone would dominate every measurement against a production-sized dataset. Keep it at the production value — not more, or local becomes faster than production and the parity rule is broken. A container created before this setting keeps its old command until it is recreated (docker compose -f docker-compose-infra.yml up -d db); check with SELECT @@innodb_buffer_pool_size.

  • JVM warm-up. The first requests measure JIT compilation, not the application. Discard them and repeat the request a dozen times before reading the percentiles.

Troubleshooting

Missing bean buildProperties

Should you encounter the error

No bean named 'buildProperties' available

This can be fixed by running

./mvnw spring-boot:build-info

Explanation: Maven creates a file named target/classes/META-INF/build-info.properties. This file might be missing when an IDE does not create it. (Happened to Klaus with IntelliJ IDEA, despite Maven Integration). (by Klaus)

Missing bean gitProperties

Should you encounter the error

No bean named 'gitProperties' available

This can be fixed by running

./mvnw git-commit-id:revision

Explanation: Maven creates a file named target/classes/git.properties. This file might be missing when an IDE does not create it. (Happened to Klaus with IntelliJ IDEA, despite Maven Integration). (by Klaus)

DB-Changes

Changes to the database are collected via Liquibase in the following file:

src/main/resources/db/changelog/db.changelog-master.yaml

Environment variables

The following environment variables must be set in each environment/stage:

SPRING_DATASOURCE_USERNAME
SPRING_DATASOURCE_PASSWORD
SPRING_DATASOURCE_URL

The following is an example for local testing with the included docker-compose file:

SPRING_DATASOURCE_USERNAME=salattest
SPRING_DATASOURCE_PASSWORD=salattest
SPRING_DATASOURCE_URL=jdbc:mysql://localhost:3306/salat?useUnicode=true&useJDBCCompliantTimezoneShift=true&serverTimezone=Europe/Berlin&useLegacyDatetimeCode=false&autoReconnect=true

Key of the secret store

Passwords and tokens of foreign systems (the JIRA replications) are stored encrypted (ADR-0038). The key comes from the environment only, never from a configuration file in the repository:

SALAT_SECRET_ACTIVEKEYID=k1
SALAT_SECRET_KEYS_K1=<output of: openssl rand -base64 32>

Every environment has a key of its own, and so does every developer locally — generate one and keep it to yourself. Without a key the application starts, but stores no password or token, and the replications do not run. A secret from a copy of another environment's database cannot be read locally; enter it again in the form of the replication.

The key id consists of lower case letters and digits only. To change the key, add the new one (SALAT_SECRET_KEYS_K2), set SALAT_SECRET_ACTIVEKEYID=k2 and restart: the start encrypts every secret with the new key, after that the old one can go.

AGENTS.md

More detailed design decisions can be found in AGENTS.md

About

Time tracking, billing and budget controlling for consulting work — a Spring Boot modular monolith.

Topics

Resources

Stars

7 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages