Skip to content

amormul/release-notifier

Repository files navigation

GitHub Release Notification Service

A PHP 8.3 monolith that exposes a REST API (and optional gRPC) for subscribing to email notifications when a GitHub repository publishes a new release. A background scanner polls the GitHub API, tracks last_seen_tag per repository, and sends mail only when the release tag changes.

The HTTP API matches the official case contract: Swagger 2.0 — paths and response status codes are unchanged.

Live demo (ngrok)

Public URL while the local Docker stack and tunnel are running:

Link
Base https://carrot-coping-gentleman.ngrok-free.dev
Subscribe (HTML) /subscribe.html
Metrics /metrics
API (example) GET /api/subscriptions?email=… — same host

Set APP_URL=https://carrot-coping-gentleman.ngrok-free.dev in .env so confirmation emails use this host.
Note: Free ngrok hostnames change when you restart the tunnel unless you use a reserved domain — update this section and APP_URL after each new ngrok URL.


Features

Area Capability
REST API Subscribe, confirm, unsubscribe, list active subscriptions — per Swagger (basePath /api).
Validation Repository must be owner/repo; invalid format → 400; not found on GitHub → 404; duplicate email+repo → 409.
GitHub Validates repos via REST; fetches releases/latest for scanning; handles 429 with Retry-After / X-RateLimit-Reset and capped retries.
Persistence PostgreSQLrepos (incl. last_seen_tag), subscriptions (tokens, confirmed flag).
Migrations SQL migrations in migrations/; applied automatically when the php container starts (bin/migrate.php).
Email Confirmation link and new-release notices via SMTP (development: Mailpit, MailHog-compatible ports).
Scanner Long-running worker: only repos with confirmed subscriptions; first seen tag sets baseline without email; new tag → notify all subscribers, then update last_seen_tag.
gRPC ReleaseNotifier.Subscribe mirrors subscribe logic; proto in proto/; server on port 50051 (plaintext) inside stack.
Redis cache Optional cache for GitHub responses (TTL 10 minutes) when REDIS_HOST is set.
API key Optional API_KEY — require X-Api-Key or Authorization: Bearer for /api/*, except confirm/unsubscribe links and /metrics.
Metrics GET /metrics — Prometheus text format (HTTP counters, GitHub cache hit/miss).
Web UI Static /subscribe.html — form that calls POST /api/subscribe (supports optional API key field).
Docker Root Dockerfile, docker-compose.yml — nginx, PHP-FPM, Postgres, Redis, mail catcher, Supervisor workers.
CI GitHub Actions — Composer, PHPUnit, php -l on src/, bin/, tests/.

Architecture

Layered monolith (clear boundaries, easy to navigate):

Layer Responsibility
App\Modules\Api Slim app, routes, controllers, middleware, JSON helpers.
App\Application Use cases: subscriptions, release scanning.
App\Domain Pure rules: RepoFormat, TokenValidator, domain exceptions.
App\Infrastructure PDO, repositories, GitHub client (+ Redis decorator), mail, migrations, metrics registry.
App\Grpc gRPC service implementation and stub wiring.

Runtime processes (inside the php image, via Supervisor):

  1. php-fpm — serves HTTP through nginx.
  2. bin/scanner.php — periodic GitHub poll + notifications.
  3. bin/grpc-server.php — gRPC server (requires ext-grpc, built in Dockerfile).

Tech Stack

  • Framework: Slim 4
  • HTTP client: Guzzle
  • Mail: PHPMailer
  • DB: PostgreSQL (PDO)
  • Cache: Predis (optional)
  • gRPC: grpc/grpc, google/protobuf, generated PHP under src/Gen/
  • Tests: PHPUnit 11

Prerequisites

  • Docker & Docker Compose
  • For local grpcurl checks (optional): grpcurl

Quick Start

git clone <repository-url>
cd software-school
cp .env.example .env   # optional; compose defaults work for local demo
docker compose up -d --build
Service URL / Port Notes
REST API http://localhost:8080/api/... Via nginx → PHP-FPM
Subscribe form http://localhost:8080/subscribe.html Static + fetch to API
Mail UI (Mailpit) http://localhost:8025 SMTP to container mailhog:1025
gRPC localhost:50051 Override host port with GRPC_PUBLISH_PORT
PostgreSQL localhost:5432 User/password in docker-compose.yml
Redis localhost:6379 Used when REDIS_HOST points to it

Configuration

Environment variables (see .env.example):

Variable Purpose
APP_URL Base URL for links in emails (confirm / unsubscribe).
APP_DEBUG Slim error details (0/1).
DATABASE_DSN PDO DSN for PostgreSQL.
GITHUB_TOKEN Recommended for higher rate limits (5000/hr vs 60/hr).
SMTP_*, MAIL_FROM, MAIL_FROM_NAME Outbound mail.
SCAN_INTERVAL_SECONDS Scanner sleep between full cycles (minimum 60s enforced in bin/scanner.php).
REDIS_HOST, REDIS_PORT If REDIS_HOST non-empty, GitHub calls go through Redis cache (600s TTL).
API_KEY If set, protects /api/* as documented below.
GRPC_PORT gRPC listen port inside container (default 50051).
GRPC_PUBLISH_PORT Host port mapped to gRPC (compose).

REST API

Authoritative spec: swagger.yaml.

Method Path Description Typical statuses
POST /api/subscribe JSON body: email, repo. Validates format & GitHub; stores pending subscription; sends confirmation email. 200, 400, 404, 409
GET /api/confirm/{token} Confirms subscription (token: 64 hex chars). 200, 400, 404
GET /api/unsubscribe/{token} Removes subscription. 200, 400, 404
GET /api/subscriptions?email= Lists confirmed subscriptions for email (Swagger “active”). 200, 400
GET /metrics Prometheus exposition (not in Swagger; no API key required). 200

API key (when API_KEY is set): send X-Api-Key: <key> or Authorization: Bearer <key>.
Exempt paths: GET /api/confirm/*, GET /api/unsubscribe/*, GET /metrics, and any non-/api path.

Example:

curl -s -X POST http://localhost:8080/api/subscribe \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","repo":"golang/go"}'

gRPC

  • Proto: proto/releasenotifier/v1/subscription.proto
  • Service: releasenotifier.v1.ReleaseNotifier
  • RPC: Subscribe — same business rules as POST /api/subscribe; response includes ok, error_message, http_status.

From the repository root (with stack running and port 50051 published):

grpcurl -plaintext \
  -import-path proto \
  -proto proto/releasenotifier/v1/subscription.proto \
  -d '{"email":"you@example.com","repo":"golang/go"}' \
  localhost:50051 releasenotifier.v1.ReleaseNotifier/Subscribe

Background Scanner

  • Selects repositories that have at least one confirmed subscription.
  • Calls GitHub GET /repos/{owner}/{repo}/releases/latest.
  • No prior tag: stores current tag as last_seen_tag, no notification email.
  • Tag unchanged: no action.
  • Tag changed: email each confirmed subscriber (with unsubscribe link), then update last_seen_tag.
  • Failures for one subscriber do not block others (errors swallowed per address in the loop).

Database

Applied migrations live in migrations/*.sql (versioned via schema_migrations).

  • reposfull_name, last_seen_tag, timestamps.
  • subscriptionsemail, repo_id, confirmed, confirm_token, unsubscribe_token, uniqueness on (email, repo_id).

Observability

  • GET /metrics — counters such as app_http_requests_total{method,route} and github_api_cache_hits_total{result} (hit/miss when Redis wrapper is active).

Development

docker compose run --rm php composer install
docker compose run --rm php vendor/bin/phpunit
  • Tests: tests/ — domain and gRPC descriptor coverage.
  • Syntax: CI runs php -l on src/, bin/, tests/.

Continuous Integration

Workflow: .github/workflows/ci.yml

  • PHP 8.3
  • composer install
  • vendor/bin/phpunit
  • php -l on application sources

Production Deployment

  1. Run with Docker (or equivalent) with a real DATABASE_DSN, APP_URL, and SMTP settings.
  2. Set GITHUB_TOKEN for reliable GitHub access.
  3. Optionally set REDIS_HOST for caching and API_KEY to restrict the REST API.
  4. Ensure Supervisor (or another process manager) runs php-fpm, scanner, and grpc-server as in this repository’s Docker image.
  5. Terminate TLS at a reverse proxy in front of nginx; publish only required ports.

Repository Layout (high level)

bin/                  # migrate, scanner, grpc-server CLI entrypoints
docker/               # nginx, php entrypoint, Supervisor configs
migrations/           # SQL migrations
proto/                # gRPC proto definitions
public/               # index.php (Slim front controller), subscribe.html
src/
  Application/        # use cases
  Domain/             # rules & exceptions
  Gen/                # generated protobuf PHP (committed)
  Grpc/               # gRPC server
  Infrastructure/     # DB, GitHub, mail, Redis, metrics, migrations
  Modules/Api/        # HTTP layer
tests/

License

Proprietary / coursework context — add a LICENSE file if you publish publicly.

About

GitHub release email notifications — PHP 8.3 / Slim, Docker, PostgreSQL, gRPC. SE School case.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors