From 96f9917adaaf07e67593311c9d3750f956fd965f Mon Sep 17 00:00:00 2001 From: podlLev Date: Mon, 3 Aug 2026 08:42:55 +0300 Subject: [PATCH 1/4] ci: add coverage-badge job to generate and commit JaCoCo badge --- .github/workflows/ci.yml | 41 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ac8ae7b..0e926df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,7 +66,46 @@ jobs: path: target/*.jar # --------------------------------------------------------------------- - # 2. Build + push a versioned image to Docker Hub + # 2. Regenerate the coverage badge from the JaCoCo report and commit it + # - runs ONLY on: pushes to main or dev + # - the ref check alone excludes pull_request events: their ref is + # refs/pull//merge, never refs/heads/main or .../dev + # - actions/checkout defaults to the triggering ref, so the commit + # lands back on whichever branch (main or dev) was just pushed + # - reads target/site/jacoco/jacoco.csv from the `test` job above + # --------------------------------------------------------------------- + coverage-badge: + name: Update Coverage Badge + needs: test + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Download JaCoCo report + uses: actions/download-artifact@v7 + with: + name: jacoco-report + path: target/site/jacoco + + - name: Generate coverage badge + uses: cicirello/jacoco-badge-generator@v2 + with: + jacoco-csv-file: target/site/jacoco/jacoco.csv + badges-directory: .github/badges + generate-branches-badge: false + generate-summary: false + + - name: Commit badge + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "chore: update coverage badge [skip ci]" + file_pattern: .github/badges/*.svg + + # --------------------------------------------------------------------- + # 3. Build + push a versioned image to Docker Hub # - runs ONLY on: pushes to main # - every push to main -> : :latest # - pushes to dev, and all PRs -> this job does not run at all From faf53b00b3b7aad54837c55a26b5dbbb8b126602 Mon Sep 17 00:00:00 2001 From: podlLev Date: Mon, 3 Aug 2026 08:44:20 +0300 Subject: [PATCH 2/4] docs: add CI/coverage badges and architecture diagrams to README --- README.md | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 17951d2..ccc85cb 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,15 @@ PostgreSQL 17 Redis 7 Docker ready - CI GitHub Actions + CI status + Coverage

Overview · Features · Tech Stack · + Architecture · Getting Started · API Docs · Observability · @@ -74,6 +76,33 @@ WeatherViewer is a personal weather dashboard for tracking the places you care a | CI/CD | GitHub Actions (build/test, coverage, Docker Hub image push) | | External API | [OpenWeatherMap](https://openweathermap.org/api) (current weather, forecast, geocoding) | +## Architecture + +Two request paths matter most for reliability: reads that hit the OpenWeatherMap API, and emails triggered by account actions. Both are built so a slow or failing dependency degrades gracefully instead of taking the app down with it. + +**Weather read path** — a cache-aside read guarded by retry and a circuit breaker: + +```mermaid +flowchart LR + A[Controller] --> B["Cache
@Cacheable"] + B --> C["Client
retry + breaker"] + C --> D[("Weather API")] + C -. fallback .-> E["Fallback
service unavailable"] +``` + +A cache hit never reaches `WeatherApiClient`. On a miss, every outbound call is wrapped with Resilience4j: transient failures are retried with backoff, and once OpenWeatherMap is failing consistently the breaker opens and short-circuits straight to the fallback instead of piling up slow requests — so one saved location failing to load doesn't take the rest of the dashboard down with it. + +**Async mail path** — a write that only sends mail after its transaction commits: + +```mermaid +flowchart LR + F["Service
writes token"] --> G["Event
after commit"] + G --> H["Listener
@Async"] + H --> I[("SMTP")] +``` + +Verification and password-reset emails are sent from a `@TransactionalEventListener(phase = AFTER_COMMIT)`, so an email can never reference a token whose transaction rolled back. The send itself runs `@Async` on a dedicated pool, so a slow SMTP server can't add latency to the request that triggered it. `MailService` retries transient SMTP failures on its own and never throws — a failure there is logged and goes no further. + ## Prerequisites - Java 17+ @@ -215,7 +244,8 @@ Tests run against an in-memory H2 database, so no external services are required Every push to `main` and every pull request into `main`/`dev` runs through GitHub Actions: 1. **Build & Test** — compiles the project and runs the full test suite against H2, publishing a JUnit test report and a JaCoCo coverage report as workflow artifacts. -2. **Docker build & push** — on pushes to `main`, builds the application image and pushes it to Docker Hub as `podllev/weather-viewer`. +2. **Update coverage badge** — on pushes to `main`, regenerates the `.github/badges/jacoco.svg` badge from that JaCoCo report and commits it back to the repo. +3. **Docker build & push** — on pushes to `main`, builds the application image and pushes it to Docker Hub as `podllev/weather-viewer`. See `.github/workflows/ci.yml` for the full pipeline. From e0b33b6c1b1ed60a4f134610945359346cb39615 Mon Sep 17 00:00:00 2001 From: podlLev Date: Mon, 3 Aug 2026 08:48:59 +0300 Subject: [PATCH 3/4] docs: add system architecture overview diagram to README --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index ccc85cb..a1fe036 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,33 @@ WeatherViewer is a personal weather dashboard for tracking the places you care a ## Architecture +**System overview** — the app sits between the browser and four external dependencies. Every HTTP request passes through the rate limiter and the security filter chain before reaching a controller; live dashboard/forecast updates instead flow over a persistent WebSocket connection, pushed on a schedule rather than requested: + +```mermaid +flowchart TB + Client[Browser client] + RL[Rate limiter] + Sec[Security filter chain] + Web[Controllers + REST] + WS[WebSocket / STOMP] + Svc[Services] + DB[(PostgreSQL)] + Cache[(Redis)] + Weather[(OpenWeatherMap API)] + Mail[(SMTP)] + + Client --> RL --> Sec --> Web + Client -. live updates .-> WS + Web --> Svc + WS --> Svc + Svc --> DB + Svc --> Cache + Svc --> Weather + Svc --> Mail +``` + +Postgres holds users, locations, and tokens (schema managed by Liquibase). Redis backs both the rate limiter's fixed-window counters and the weather/forecast/geocoding cache. The two flows below zoom into the parts of this picture that need to tolerate a flaky dependency: weather reads and outbound mail. + Two request paths matter most for reliability: reads that hit the OpenWeatherMap API, and emails triggered by account actions. Both are built so a slow or failing dependency degrades gracefully instead of taking the app down with it. **Weather read path** — a cache-aside read guarded by retry and a circuit breaker: From a13ac365524dd5b9f87c8d62f7e52482f2b12b53 Mon Sep 17 00:00:00 2001 From: podlLev <92114713+podlLev@users.noreply.github.com> Date: Mon, 3 Aug 2026 05:54:49 +0000 Subject: [PATCH 4/4] chore: update coverage badge [skip ci] --- .github/badges/jacoco.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/badges/jacoco.svg diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg new file mode 100644 index 0000000..3d59126 --- /dev/null +++ b/.github/badges/jacoco.svg @@ -0,0 +1 @@ +coverage99.4% \ No newline at end of file