Angel Campa
Colour theme

WorkThe .NET year

GeoLeap

Told you which countries and streaming services carried a show or movie, and suggested a VPN destination when it wasn't available where you were.

Ran on
Railway (API), Cloudflare Workers (web, via OpenNext)
Ran
August 2025 to July 2026
Domain
Streaming availability and VPN discovery
Source
streaming-availability-app-geoleap-snapshot

Built with

  • .NET 9
  • PostgreSQL
  • Redis
  • Hangfire
  • SignalR
  • Next.js 16
  • React 19
  • React Native / Expo
  • Cloudflare Workers
  • Stripe

Hosted services

  • Neon (PostgreSQL)
  • Redis
  • Stripe
  • Resend
  • Sentry
  • MaxMind GeoIP2
  • RapidAPI (streaming-availability)
GeoLeap marketing home page, above the fold: the headline "Find the country where your show is streaming before you open your VPN," the "Search 57 countries before you switch servers" tagline, a title search box with example title chips (Breaking Bad, The Office, Squid Game, Friends), and a Get Started Free button
The top of the public home page, captured from the local stack against seeded data.

The decision

Every availability answer GeoLeap gave cost money: the data came from one commercial API billed per call. ApiCostManager sat on the request path and stopped a call before it went out once spend hit a ceiling of $200 a month and $10 a day: real budget enforcement on a dependency billed by the call.

A second implementation, BudgetManager, checked a per-provider limit alongside the daily and monthly totals, and served an admin reporting endpoint. Both checks failed open on a database or cache error, returning true from a catch block under a comment that recorded the choice.

Architecture

Three clients (a Next.js 16 web app, a React Native mobile app, and a Playwright end-to-end suite) sat in front of one .NET 9 API of 68 controllers and 905 HTTP endpoints, backed by PostgreSQL, Redis, Stripe, and a single paid streaming-availability API.

Like GatherGrove, the schema itself changed mid-project: 202 entities were squashed into a single PostgreSQL baseline migration when the project cut over from SQL Server in February 2026.

The API’s types were declared twice, once under frontend/src/types/ and once under mobile/src/types/, with no shared package between the two clients to hold them in step. The web app also carried 19 route handlers of its own instead of forwarding straight through to the API, because a straight pass-through does not forward Set-Cookie back to the browser and the session cookie was HTTP-only.

Two places refused to trust their own configuration. Stripe verifies a webhook signature against the raw, unmodified bytes, so EnableBuffering() runs on /api/webhooks ahead of the correlation-id middleware that would otherwise read the body first. And the API would not start in Production without three unique indexes on MobileSubscriptions: AssertRequiredProductionIndexesAsync looks for them and stops the process, because without them nothing errors and purchases silently double-count.

Eleven background services ran in process, gated on ENABLE_BACKGROUND_SERVICES, alongside Hangfire on a switch of its own.

SensitiveDataFilter, a redaction utility carrying 54 sensitive field names and six regex patterns, already sat in the codebase when a December 2025 audit found two places that logged raw bodies ahead of it: the request and response logging middleware, and the Stripe webhook controller, which was writing the webhook secret itself in plaintext. Routing both through the filter, shipped 2025-12-17, closed it, backed by 75 new sanitization test cases. One limit is written down rather than left quiet: the card regex expects 16 digits and does not match a 15-digit Amex number, so those redact by field name only.

Closed the same day: a double click, a network retry or a second browser tab could each have produced a duplicate charge, so the fix added an idempotency key checked in the application and backed by a unique index in the database. A test fires 10 concurrent requests under one key and asserts that at most one transaction row exists afterward.

By the numbers

Each figure carries where it came from and when it was measured. Open one to see the command behind it, where the repository recorded a command.

Total source
1,063,755 lines across 2,675 files (C#, TSX, TS, JS, SQL)
Provenance

Source portfolio/METRICS.md § Lines and files, counted with wc -l

find . -type f -name "$1" -not -path "*/node_modules/*" -not -path "*/bin/*" -not -path "*/obj/*" -not -path "*/.next/*" -not -path "*/coverage/*" -print0 | xargs -0 cat | wc -l

Measured

API surface
68 controllers, 905 HTTP endpoints
Provenance

Source portfolio/METRICS.md § API surface

grep -rho '\[Http\(Get\|Post\|Put\|Delete\|Patch\)' backend/GeoLeap.Api --include='*.cs' | wc -l

Measured

Database
202 DbSet<> entities, 215 tables
Provenance

Source portfolio/METRICS.md § API surface (215 confirmed by migrating an empty PostgreSQL 16 instance)

Measured

Backend tests
6,527 tests passing
Provenance

Source portfolio/METRICS.md § Test results: Backend

cd backend && ENABLE_COVERAGE=true dotnet test --settings coverlet.runsettings --collect:"XPlat Code Coverage"

Measured

Frontend tests
7,786 tests passing
Provenance

Source portfolio/METRICS.md § Test results: Frontend

cd frontend && npm run test:coverage

Measured

Commits
2,322 across 140 active days
Provenance

Source portfolio/METRICS.md § Commit history

git rev-list --count HEAD

Measured

Pre-launch audit sweep (20 working days, two-thirds through the build)
174 bugs found, 108 closed within the sweep
Provenance

Source portfolio/DEVELOPMENT-HISTORY.md § A 20-day audit, mid-build

Measured

Screens

GeoLeap admin panel showing a widget picker, key metrics (12,847 customers, $284,750 revenue, 9,234 subscriptions, 47 support tickets), recent customers and a support ticket queue
The admin dashboard, captured with generateMockMetrics() supplying the customer, revenue and subscription figures shown as seeded demo data.
GeoLeap upgrade page showing GeoLeap Premium at $15 a year with a 30-day free trial, a feature checklist, a why-upgrade panel, and a 14-day money-back guarantee card
The upgrade page renders static plan copy, so it displays fully on a fresh account.
A generated GeoLeap platform page for Netflix, showing pricing, key features, popular titles and the 32 countries it is available in
A generated platform page, one of roughly 250 programmatic marketing pages built from static content.
A generated GeoLeap page comparing Netflix and Hulu, with a verdict summary, a ten-row side-by-side table naming a winner per category, and a Frequently Asked Questions block
A generated comparison page.
Read the repositorystreaming-availability-app-geoleap-snapshot

Its portfolio/ directory holds the architecture, metrics and testing documents these figures were taken from.