← projects
$ Contribution Radar
Turns a GitHub username or a programming language into a curated list of active, contributable open source issues.
- →Built with a hexagonal Spring Boot backend and a Vite/React frontend. Given a GitHub username, it analyzes the account's most used languages via the GitHub GraphQL API and surfaces open issues weighted toward those languages; alternatively, a language can be searched directly without any username.
- →Fixed a race condition in per search statistics tracking by replacing a read then write JPA flow with a single atomic PostgreSQL INSERT ... ON CONFLICT DO UPDATE upsert, eliminating duplicate key failures under concurrent requests.
- →Discovered that GitHub's own stars: search qualifier silently over filtered results server side; replaced it with client side post filtering against the real stargazerCount, combined with an activity window filter so only issues updated in the last 90 days or with meaningful discussion are suggested.
- →Resolved a hidden N+1 style delete pattern in a scheduled cache cleanup job deleting entities one by one triggered a separate DELETE per entity plus its @ElementCollection child table. Replaced it with two bulk native SQL DELETE statements, cutting the job to a fixed number of queries regardless of batch size.
- →Implemented IP based abuse protection as a single Servlet Filter chain instead of per controller checks: a ClientIpResolver that scans X-Forwarded-For right to left to resist IP spoofing behind the reverse proxy, automatic temporary bans after repeated invalid requests, and separate Caffeine backed rate limits per endpoint.
- →Deployed with Docker Compose (PostgreSQL, Spring Boot, Vite served via nginx) joined to an existing shared nginx proxy + Let's Encrypt network, with an nginx SPA fallback so client side routes resolve correctly on direct access and for search engine crawlers.
- →Hardened the frontend's nginx layer against exploit scanning bots with a categorized blocklist (403 on credential files, CMS admin paths, backup/dump extensions, editor artifacts) and per IP leaky bucket rate limiting (limit_req, tolerating real traffic bursts while rejecting scripted rapid fire requests), validating both end to end in an isolated, disposable Docker environment before touching the real reverse proxy a process that also surfaced a real config bug, an error_page rule pointing at a nonexistent file that would have shown users a misleading 404 instead of a proper 503 when the rate limit triggered.
- →Prototyped a fail2ban based IP banning layer on top of the rate limiter, since nginx's limiter has no memory across requests; verified real ban and auto expiry behavior against fake log data inside an isolated container network, de risking the approach with zero exposure to the production server before deciding whether to deploy it.
- →Diagnosed, from real production log analysis, that the frontend sits behind a two-hop chain (Cloudflare, then a shared reverse proxy) that left the rate limiter blind to real visitor IPs, silently throttling all traffic as a single shared address; fixed it by trusting both hops (Cloudflare's official IP ranges plus the internal Docker subnet) and recursively resolving the X-Forwarded-For chain, verified against live production traffic.
- →Closed a GitHub search-qualifier injection surface (e.g. is:closed, -label:...) in the free text keyword filter by restricting it to a safe character whitelist at the domain layer, covered by 20 unit tests; confirmed the username input was already immune since it's validated against GitHub's own username format and passed as a GraphQL variable rather than concatenated into the query document.
- →Implemented author-association badges end to end (GraphQL to cache to UI), surfacing whether an issue was opened by a project maintainer/owner or a past contributor, verified via live schema introspection and real production data across every observed association type.
- →Set up an automated nightly database backup pipeline (pg_dump, gzip, checksum verification) uploaded to Cloudflare R2 with retention cleanup and an HTML email report from a domain-authenticated sender, and separately fixed the shared reverse proxy's unbounded, multi-hundred-megabyte log file with host-level log rotation, without touching the running container.
- →Added a non-personalized RSS feed for the language search by reusing the existing per-search cache table under a reserved global sentinel key rather than building new infrastructure, keeping GitHub API cost bounded regardless of visitor volume; RSS 2.0 XML is hand-rendered without an external library, with manual entity escaping and RFC-822 date formatting.
- →Added a second, non-personalized discovery mode, Popular Projects, surfacing recent issues from only well-known, high-star repositories (5k/10k presets) with a cache shared across all visitors; when the same stars: reliability issue resurfaced for this feature's own repo-scoped search, fixed it architecturally instead of just re-filtering, by first discovering a pool of qualifying repositories through GitHub's repository-type search (where stars: is verified accurate) with a much longer, weekly cache, then scoping the issue search to that pool via repo: qualifiers confirmed live that a single query accepts up to 100 chained repo: qualifiers without truncation.
- →Tuned Popular Projects after live testing showed a recency-sorted single page produced too few results once correctly filtered: added GraphQL cursor pagination across three pages, raised the per-repository result cap specifically for this mode to preserve diversity without starving well-known repos, and extended the existing per-IP Caffeine rate limiter to cover both this endpoint and the RSS feed at a shared, explicitly chosen 20 requests/minute; added a lightweight in-memory counter that increments only on an actual GitHub refresh, never on a cache hit, to track real usage separately from the click-based counters already in place.
- →Discovered that GitHub's type: ISSUE search silently mixes in pull requests alongside real issues, wasting up to 90% of every page's budget on results our own code was already discarding; fixed by adding an is:issue qualifier everywhere issues are searched, verified live against the real API (3 of 30 real issues before the fix, 30 of 30 after) for a roughly 10x increase in usable results at zero additional API cost.
- →Diagnosed a silent, 100%-reproducible bug from real production log analysis: cross-origin POST requests to a search-statistics endpoint were being rejected with a CORS 403 because the CORS policy only allowlisted GET, while the frontend's own call failed silently by design so no user ever saw an error; reproduced the exact failure with a single curl request (with vs without an Origin header) before shipping the one-line fix, then confirmed the corrected response live in production.
- →Found and fixed a subtler bug in the IP-ban cache: its Caffeine expiry was a fixed multi-day duration that reset every time the cache reloaded from Postgres on startup, so an application restart near the end of a real ban would silently extend it; replaced the fixed expiry with a custom per-entry policy driven by each ban's actual expiration timestamp, verified with dedicated tests that simulate the exact restart-reload scenario rather than assuming the fix worked.
JavaSpring BootReactTypeScriptPostgreSQLDockerHexagonal ArchitectureGitHub GraphQL APIResilience4j