Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

36 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DevPulse

Automated GitHub activity tracking with AI-generated weekly team reports delivered to Slack.

Java Spring Boot PostgreSQL Docker CI


The Problem

Small dev teams have a visibility problem. Every Friday, engineering leads are left asking — what did we actually ship this week? GitHub Insights shows graphs but no narrative. Jira is too heavyweight for most teams. Nobody writes changelogs manually.

DevPulse solves this with zero behaviour change from the team. It watches your GitHub activity silently, computes meaningful weekly metrics, and delivers a human-readable AI-generated summary to your Slack channel every Monday morning. Nobody logs into a dashboard. Nobody fills out a form.


How It Works

Developer pushes code
        │
        ▼
POST /api/webhooks/github
        │  HMAC-SHA256 signature verification
        │  Idempotency check via delivery ID
        ▼
  raw_events table (PostgreSQL)
        │
        │  Every night at 2am
        ▼
MetricsComputationService
        │  10 derived metrics computed from raw payloads
        │  Commit counts, PR lifecycle, contributor breakdown,
        │  bug/feature ratio, most changed file, velocity
        ▼
weekly_metrics table (PostgreSQL)
        │
        │  Every Monday at 9am
        ▼
    Gemini API  ──►  AI-generated professional summary
        │
        ▼
    Slack channel  ◄──  Team receives digest
        │
        ▼
delivery_logs table (full audit trail)

Two decoupled worlds running in the same process:

  • Reactive — webhook ingestion, responds in milliseconds, always available
  • Scheduled — nightly analysis and Monday reporting, completely independent

Sample Slack Output

Week of April 13th recorded 6 total commits, all from Bhavya-Sonigra. The most frequently modified file was WebhookController.java, with changes also touching GeminiService.java and MetricsComputationService.java. Commit messages indicate 2 new features shipped and 1 bug fix resolved.

No pull requests were opened, merged, or remain in review this week. Average PR review time is not applicable for this period.


The 10 Computed Metrics

Every night, MetricsComputationService derives these from raw GitHub payloads:

Metric Source
Total commits Push event commit arrays
Commits per contributor Push event pusher field
Top contributor Max of commits per user map
Most changed file Union of added + modified across all commits
Bug fix count Commit messages matching: fix, bug, patch, hotfix, resolve, issue
Feature count Commit messages matching: feat, feature, add, new, implement
PRs opened pull_request events with action: opened
PRs merged pull_request events with action: closed + merged: true
PRs still open pull_request events with state: open
Average PR open hours Time delta from created_at to merged_at

Architecture Decisions

Why store raw webhook payloads before processing? Event sourcing — raw GitHub JSON is stored verbatim before any analysis. If computation logic changes or has a bug, metrics can be recomputed from the original data. The source of truth is never lost.

Why three separate tables? raw_events is write-heavy, append-only, the source of truth. weekly_metrics is read-heavy, derived, recomputable. delivery_logs is audit-only, append-only. Different access patterns, different lifecycles, clean separation.

Why constant-time signature comparison? Standard string comparison exits on the first mismatched character. An attacker measuring response latency can reconstruct the valid HMAC one character at a time. constantTimeEquals always runs to completion — timing reveals nothing.

What does @Transactional protect in metrics computation? Saving WeeklyMetrics and marking raw events as processed = true must be atomic. If the app crashes between these two operations, events would be permanently missed. The transaction rolls both back on failure — next run starts clean.

Why RestTemplate over WebClient? Gemini and Slack are called once per week from a scheduled job. The complexity of reactive HTTP is not warranted. Synchronous RestTemplate with explicit 5s connect / 30s read timeouts is the correct fit for this access pattern.


Tech Stack

Layer Technology Why
Language Java 21 LTS, modern text blocks and records
Framework Spring Boot 4 Industry standard Java backend
ORM Spring Data JPA + Hibernate 7 Zero SQL for standard operations
Database PostgreSQL 15 Relational structure for time-series queries
AI Gemini 2.5 Flash Natural language report generation
Delivery Slack Incoming Webhooks Zero-friction team channel delivery
Container Docker + docker-compose Identical environment across dev and CI
CI GitHub Actions Build, test, Docker image on every push

API Reference

Webhook Ingestion

Method Endpoint Description
POST /api/webhooks/github Receive GitHub push and pull_request events

Required headers:

X-Hub-Signature-256: sha256={hmac}
X-GitHub-Event: push | pull_request
X-GitHub-Delivery: {uuid}

Control & Query

Method Endpoint Description
GET /api/metrics/latest Latest week's computed metrics
GET /api/metrics/history Past 90 days of metrics
GET /api/events/recent Unprocessed events from last 24 hours
GET /api/deliveries All Slack delivery records with status
POST /api/report/trigger Manually trigger AI report generation
POST /api/analysis/trigger Manually trigger nightly analysis

Sample Response — /api/metrics/latest

{
  "weekStart": "2026-04-13",
  "totalCommits": 6,
  "prsOpened": 0,
  "prsMerged": 0,
  "prsStillOpen": 0,
  "avgPrOpenHours": null,
  "topContributor": "Bhavya-Sonigra",
  "bugFixCount": 1,
  "featureCount": 2,
  "mostChangedFile": "WebhookController.java",
  "commitsByUser": "{\"Bhavya-Sonigra\": 6}",
  "computedAt": "2026-04-18T02:00:03"
}

Running Locally

Prerequisites

  • Java 21+
  • Maven 3.9+
  • Docker Desktop running

1. Clone

git clone https://github.com/Bhavya-Sonigra/Devpulse.git
cd Devpulse

2. Environment variables

Create .env at the project root:

GITHUB_WEBHOOK_SECRET=your-webhook-secret
GEMINI_API_KEY=your-gemini-api-key
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/xxx/yyy/zzz

3. Start PostgreSQL

docker-compose up postgres -d

4. Run

mvn spring-boot:run

5. Verify

curl http://localhost:8080/actuator/health
# {"status":"UP"}

Full stack with Docker

docker-compose up --build

GitHub Webhook Setup

Local development with ngrok

ngrok http 8080
# Copy the https://xxx.ngrok-free.app URL

Configure on GitHub

  1. Your repo → SettingsWebhooksAdd webhook
  2. Payload URL: https://your-domain/api/webhooks/github
  3. Content type: application/json
  4. Secret: matches your GITHUB_WEBHOOK_SECRET
  5. Events: select Pushes and Pull requests

GitHub sends a ping immediately — check your logs to confirm.


Testing the Full Pipeline

# 1. Push a real commit (triggers live webhook)
git commit -m "feat: test full pipeline" --allow-empty && git push

# 2. Confirm event was stored
curl http://localhost:8080/api/events/recent

# 3. Run nightly analysis manually
curl -X POST http://localhost:8080/api/analysis/trigger

# 4. Check computed metrics
curl http://localhost:8080/api/metrics/latest

# 5. Generate and deliver AI report
curl -X POST http://localhost:8080/api/report/trigger

# 6. Check audit trail
curl http://localhost:8080/api/deliveries

Project Structure

src/main/java/com/devpulse/
├── config/
│   ├── AppConfig.java               # Environment variable binding
│   └── RestTemplateConfig.java      # HTTP client + ObjectMapper beans
├── controller/
│   ├── WebhookController.java       # POST /api/webhooks/github
│   └── MetricsController.java       # Query and trigger endpoints
├── exception/
│   ├── InvalidSignatureException.java
│   └── GlobalExceptionHandler.java  # Centralised error handling, no stack traces leaked
├── model/
│   ├── entity/
│   │   ├── RawEvent.java            # raw_events table
│   │   ├── WeeklyMetrics.java       # weekly_metrics table
│   │   └── DeliveryLog.java         # delivery_logs table
│   └── payload/
│       ├── PushEventPayload.java    # GitHub push event shape
│       ├── PullRequestEventPayload.java
│       └── GeminiResponse.java      # Gemini API response + extractText()
├── repository/
│   ├── RawEventRepository.java      # Method-name queries, custom @Query batch update
│   ├── WeeklyMetricsRepository.java
│   └── DeliveryLogRepository.java
├── scheduler/
│   ├── AnalysisScheduler.java       # @Scheduled 2am daily
│   └── ReportScheduler.java         # @Scheduled 9am every Monday
├── service/
│   ├── WebhookAuthService.java      # HMAC-SHA256 + constant-time comparison
│   ├── WebhookProcessorService.java # Parse, deduplicate, persist
│   ├── MetricsComputationService.java # 10-metric computation engine
│   ├── GeminiService.java           # Prompt construction + AI call
│   └── SlackDeliveryService.java    # HTTP delivery + audit logging
└── DevpulseApplication.java         # @EnableScheduling entry point

Environment Variables

Variable Description Required
GITHUB_WEBHOOK_SECRET Shared secret for HMAC signature verification Yes
GEMINI_API_KEY Google AI Studio API key Yes
SLACK_WEBHOOK_URL Slack Incoming Webhook URL Yes
SPRING_DATASOURCE_URL PostgreSQL JDBC URL (docker-compose sets automatically) Production
SPRING_DATASOURCE_USERNAME Database username Production
SPRING_DATASOURCE_PASSWORD Database password Production

Known Limitations

Limitation Production Solution
@Scheduled jobs don't persist across restarts Quartz Scheduler with DB-backed job store
Single team, single repo — no user isolation Multi-tenancy: team_id on every table, JWT auth
No authentication on control endpoints Spring Security + JWT
No retry on external API failure Resilience4j retry with exponential backoff
Two instances both run scheduled jobs ShedLock distributed lock
Raw events accumulate indefinitely Retention policy: archive after 90 days

What's Next — V2

V2 introduces self-service onboarding so any team can connect in under 5 minutes:

  • GitHub OAuth — login with GitHub, no account creation
  • Automated webhook registration — GitHub API call registers webhook programmatically, user never touches GitHub settings
  • Slack OAuth — bot token via OAuth, user picks channel from dropdown
  • Multi-tenancy — complete data isolation between teams via team_id scoping
  • Next.js dashboard — connect page, metrics visualisation, report history
  • Trend detection — week-over-week velocity comparison, contributor concentration alerts

Built by Bhavya Sonigra · LinkedIn

About

Java backend that ingests GitHub webhook events via HMAC-SHA256 verified endpoints, computes 10 weekly metrics nightly, and delivers AI-generated team digests to Slack every Monday via Gemini API. Spring Boot · PostgreSQL · Docker · GitHub Actions

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages