Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/badges/jacoco.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 40 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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/<number>/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 -> :<pom-version> :latest
# - pushes to dev, and all PRs -> this job does not run at all
Expand Down
61 changes: 59 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
<img src="https://img.shields.io/badge/PostgreSQL-17-4169E1?logo=postgresql&logoColor=white" alt="PostgreSQL 17">
<img src="https://img.shields.io/badge/Redis-7-DC382D?logo=redis&logoColor=white" alt="Redis 7">
<img src="https://img.shields.io/badge/Docker-Ready-2496ED?logo=docker&logoColor=white" alt="Docker ready">
<img src="https://img.shields.io/badge/CI-GitHub%20Actions-2088FF?logo=githubactions&logoColor=white" alt="CI GitHub Actions">
<a href="https://github.com/podlLev/WeatherViewer/actions/workflows/ci.yml"><img src="https://github.com/podlLev/WeatherViewer/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI status"></a>
<img src="https://raw.githubusercontent.com/podlLev/WeatherViewer/main/.github/badges/jacoco.svg" alt="Coverage">
</p>

<p align="center">
<a href="#overview">Overview</a> ·
<a href="#features">Features</a> ·
<a href="#tech-stack">Tech Stack</a> ·
<a href="#architecture">Architecture</a> ·
<a href="#getting-started">Getting Started</a> ·
<a href="#api-documentation">API Docs</a> ·
<a href="#observability">Observability</a> ·
Expand Down Expand Up @@ -74,6 +76,60 @@ 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

**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:

```mermaid
flowchart LR
A[Controller] --> B["Cache<br/>@Cacheable"]
B --> C["Client<br/>retry + breaker"]
C --> D[("Weather API")]
C -. fallback .-> E["Fallback<br/>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<br/>writes token"] --> G["Event<br/>after commit"]
G --> H["Listener<br/>@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+
Expand Down Expand Up @@ -215,7 +271,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.

Expand Down