Skip to content

The Live-Built Showcase.

This page is an evolving experiment in digital architecture. It serves as a live-built showcase of my skills where every iteration is documented, from initial wireframes to advanced features.

Quick Stats

Status
Usable MVP
Rollout Time Time tracked for initial rollout only. Significantly more time has been spent on improvements and iteration.
32 hours
See roadmap for breakdown.
One-Time Cost
€3
Recurring Cost
€0/month
Source
GitHub

Engineered With

Frontend

  • Astro
  • Tailwind CSS
  • Supabase Auth
  • Cloudflare Pages

Backend

  • C# ASP.NET Core
  • PostgreSQL
  • Marten Event Sourcing

Infrastructure

  • Oracle Cloud VPS
  • Supabase
  • GitHub Actions
    • Build + Test + Deploy
    • E2E Tests (Playwright)

Goals & Requirements

Personal Showcase

Present myself to potential employers and recruiters.

Skills Demonstration

Document the building process as part of the showcase — architecture, decisions, and progress.

Technology Playground

Experiment with new technologies in a real, deployed project.

  • Event sourcing
  • Free Cloudflare Pages
  • Supabase ecosystem
  • Temporal

Non-Functional Requirements

Automation Minimal manual overhead. Infrastructure as code or GitHub Actions for everything.
Reliability The site must be up reliably.
Cost As cheap as possible — preferably free. Backend hosting: Oracle Cloud Always Free (fallback: Hetzner VPS ~€4/month).

Architecture Overview

CLOUDFLARE DNS DNS / Domain Astro Static Site (CDN) ORACLE CLOUD VPS Caddy Reverse Proxy ASP.NET Core Backend API Blue/Green Temporal Background Jobs SUPABASE PostgreSQL Database Auth Authentication (JWT) File Storage Avatars / Files CI / CD GitHub Actions Backend tests Frontend tests E2E tests BE deploy to VPS FE deploy to Cloudflare OBSERVABILITY Sentry PostHog NOTIFICATIONS Slack Brevo Email (SMTP)

Architecture Decisions

Key technical decisions documented as Architecture Decision Records.

Backend Hosting → Oracle Cloud

Oracle Cloud Always Free ARM A1 instance, with Hetzner VPS as fallback.

Context

Need a host that supports long-running processes (ASP.NET Core + Temporal background workers). Most free tiers are serverless/scale-to-zero and shut down the process when idle.

Decision

Start with Oracle Cloud Always Free ARM A1 instance (4 OCPUs, 24 GB RAM, 200 GB storage). If Oracle proves unreliable, fall back to Hetzner VPS (~€4/month).

Why

  • Free tier with generous specs (4 OCPUs, 24 GB RAM, 200 GB storage)
  • Supports long-running processes and Docker
  • Upgrading to PAYG (still free) disables idle reclamation

Known Risks

Risk Details Mitigation
Signup rejection Oracle rejects many signups based on region demand and payment method Use real credit card, match billing address, pick less popular region
ARM capacity Popular regions often have no A1 capacity available Choose EU or less popular region at signup
Idle reclamation Instances with <20% CPU at P95 over 7 days are stopped after 1 week notice Real workloads stay above threshold; PAYG disables this entirely
Account suspension Rare reports of accounts suspended without clear reason Keep PAYG enabled; maintain real usage

Setup Notes

  • Oracle Linux 8 aarch64 — ships with Podman out of the box, optimized for Oracle Cloud hardware
  • Set a large boot volume (~150 GB) at creation to avoid iSCSI block volume complexity
  • Open ports in both OCI security list and OS firewall

Alternatives Considered

Hetzner VPS (~€4/month) — Reliable fallback. Full control, predictable. Will switch here if Oracle causes trouble.
Azure App Service Free (F1) — 60 min CPU/day, no always-on — app sleeps after inactivity.
AWS Lambda / Google Cloud Run — Serverless — no persistent background process for Temporal workers.
Render free tier — Spins down after 15 min inactivity. Background workers are paid only.
Fly.io — Free tier has gotten stingier; likely ends up costing a few dollars anyway.
Azure Container Apps — Free tier is generous, but self-hosting Temporal would be awkward.

Frontend Framework → Astro SSG

Astro in SSG mode with Vue components hydrated only where needed.

Context

Need a frontend framework for a mostly-static site hosted on Cloudflare Pages. Requirements: Tailwind CSS, i18n (Czech/English), progressive interactivity, minimal JS shipped to users.

Decision

Astro in static site generation (SSG) mode. Interactive components use vanilla JS, with the option of Vue islands for complex UI logic. No Cloudflare Workers — Pages serves plain HTML/CSS/JS from CDN.

Why

  • Free forever — SSG on Cloudflare Pages has no per-request cost, unlike SSR with Workers which bills per invocation
  • Every page is pre-built and cached on CDN edge nodes worldwide — near-instant loading
  • Ships zero JS by default — only interactive islands include client-side code
  • Built-in i18n routing and locale detection
  • Cloudflare acquired Astro (Jan 2026) — best-in-class Pages integration

Alternatives Considered

Vue/Nuxt (SSR) — Second-best option — hosted preferably as a Cloudflare Worker, or potentially as a Docker image on the VPS. But SSG is simpler and free, so starting with that as long as it works.
Vanilla ES6 modules — Still needs a Tailwind build step. Would have to manually build routing, i18n, and component system.
React/Next.js — Ships ~80-120 KB React runtime. JS-first authoring. Next.js is Vercel-aligned, not Cloudflare-aligned.
SvelteKit — Good framework, but i18n is not built-in. More geared toward interactive apps.
Vercel hosting — SSR model runs server-side functions — effectively a second backend. Goal is a thin static client with a single ASP.NET Core backend.

Event Sourcing → Marten on PostgreSQL

Marten framework on PostgreSQL instead of a custom implementation on MongoDB.

Context

Event sourcing was a given — the question was whether to build it from scratch on MongoDB or use an established framework. This is the first event sourcing implementation, so learning curve and speed matter.

Decision

Use Marten on PostgreSQL — a mature .NET event sourcing framework that runs on the same database we already have.

Why

  • Already experienced with SQL but not MongoDB — faster to get up and running
  • No extra infrastructure — reuses the existing Supabase PostgreSQL, no need to run MongoDB
  • Inline snapshot projections maintain read models with zero extra code
  • Mature framework with good documentation — faster to learn than building from scratch
  • Wanted to try Supabase ecosystem — that meant PostgreSQL, and Marten fits naturally

Alternatives Considered

Build it myself on PostgreSQL — Easier indexing and concurrency control than Marten. But requires manual schema migrations as the model evolves — Marten stores events as JSON, avoiding this entirely.
Build it myself on MongoDB — Globally distributed multi-master DB with a free tier on Azure Cosmos DB. But no experience with MongoDB transactions and concurrency control — too much unknown for a first event sourcing implementation.

Auth → Supabase Auth

Use Supabase's built-in auth instead of building custom or using a separate provider.

Context

The app needs to act as an OAuth provider — users will authenticate via OAuth when connecting through MCP. Also needs standard email/password login.

Decision

Use Supabase Auth — it's already part of the Supabase ecosystem we chose for the database, supports OAuth out of the box, and runs locally in Docker for development.

Why

  • Already using Supabase for the database — auth comes included, no extra service to manage
  • Supports acting as an OAuth provider — needed for MCP, where users authenticate with the app via OAuth
  • Runs locally in Docker — full auth flow works offline without external dependencies

Alternatives Considered

Custom auth implementation — Full control, but building OAuth flows, token management, and security from scratch is complex and error-prone.
WorkOS / Clerk / Auth0 — Mature auth providers, but adds another external dependency and cost. Some don't run locally. Supabase auth is already included — no reason to add a separate service.

Code Organization → Vertical Slices

Code organized by feature, not by technical layer.

Context

Need a backend code organization strategy that keeps related code together and avoids the "change one feature, touch five layers" problem.

Decision

Code is organized by feature (e.g., Features/JobOffers/) rather than by technical layer. Each feature folder contains its controller, events, DTOs, and handlers.

Why

  • Adding a new feature = adding a new folder with all its files, no changes to shared layers
  • Each feature is self-contained — easy to understand, review, and test in isolation
  • Avoids the typical layered architecture problem where a single change touches Controllers/, Services/, Models/, DTOs/, etc.

Testing Strategy

Mock as little as possible. Real database everywhere, full-stack E2E with working auth.

Context

Need a testing approach that provides high confidence with minimal mocking, covering the full stack from API endpoints to database.

Decision

Three layers of testing, all running in parallel in CI/CD. Each blocks deployment on failure.

Why

  • Backend integration tests (main focus): xUnit + Testcontainers — real PostgreSQL, full API via WebApplicationFactory. Mock as little as possible (auth is mocked, external services like Slack/email will be mocked), but the database is real so most functionality is easy to assert
  • Backend unit tests: for domain logic and pure functions where integration tests would be overkill
  • Frontend page tests: Playwright — builds the static site, verifies rendering, navigation, i18n, and dark mode
  • E2E tests: Playwright against the full running application — working auth, working DB, everything real. Only external systems like Slack and email can't be asserted
  • CI/CD: All three test suites run in parallel, all block deployment on failure
Timeline

Development Roadmap

6 versions from static site to advanced backend features. Each version adds a layer of complexity.

Version 1

Simple Site

~7 hours · Mar 27, 2026

Simple site with domain, hosting and deployment pipeline.

What was done
  • Pavel Set up domain, DNS and Cloudflare Pages hosting
  • Claude Designed pages in Google Stitch, built with Astro + Tailwind
  • Claude GitHub Actions deploy pipeline
  • Claude Light and dark theme with system preference detection
  • Claude Language picker (English / Czech) with per-page translations
Version 2

Backend Included

~25 hours · Mar 28 – Apr 1, 2026

Could do it in ~10 hours next time.

Time tracked covers the initial rollout only. Improvements and iteration add significantly more.

ASP.NET Core backend with Supabase Auth and Marten event sourcing. Used for managing job offers — users can submit and track offers, admins can review and update statuses.

What was done
  • Pavel Supabase project and Google OAuth setup
  • Pavel Oracle Cloud VM, PostgreSQL, Caddy reverse proxy
  • Claude ASP.NET Core Web API with vertical slices and Marten event sourcing
  • Claude Supabase Auth with email/password + Google OAuth, account linking and avatar upload
  • Claude Job offer form with file upload, user dashboard and admin view
  • Claude Docker setup, CI/CD pipeline to Oracle Cloud with blue/green deployment
  • Claude Integration tests (Testcontainers) and E2E tests (Playwright)
  • Claude SEO: sitemap, robots.txt, llms.txt, styled 404 page
  • Pavel Optimizing CI/CD pipeline
  • Pavel Improving run configs for DevX
  • Pavel Restructuring code a few times
  • Claude Cloudflare Turnstile CAPTCHA on job offer submission
Version 3

Emails, Slack & Observability

Email confirmations and Slack notifications on job offer submissions and status changes. Full observability stack: Sentry for errors, traces, and logs, PostHog for product analytics.

Version 4

Background Tasks

Move emails and notifications into background processing. Self-hosted Temporal on the backend VPS with retry semantics. Fallback: Azure Queue Storage.

Version 5

MCP Server

Expose job offer management via a Model Context Protocol server. Users can submit, track, and manage their job offers through any MCP-compatible client.

Version 6

Pay to Win (Stripe)

Monetize job offer submissions via Stripe with tiered pricing.

Tier Price Guarantee
Free$0Response within 7 days
Premium$5Response within 24 hours
Interview (30 min)$25Call within 7 days
Interview (1 hour)$50Call within 7 days

Lessons Learned

Opinions formed after building and shipping with these technologies.

Using Supabase

Verdict

Would probably do it again just for the setup simplicity on personal projects. Not on commercial projects with real budgets.

Observations

  • Free PostgreSQL is cool and worth it on its own
  • Auth has some rough edges — e.g. email provider issues ( see discussion )
  • File storage is cheap everywhere — S3 or Azure Blobs are just as cheap and I'd trust them more than Supabase Storage
  • Using Supabase slows down the dev CLI compared to just using a plain PostgreSQL Docker image
  • There's no real pressure to use the bundled file storage and auth — but you kinda feel like an idiot if you don't, since it's right there

Using Event Sourcing / Marten

Verdict

Would probably write some custom stuff next time rather than using Marten as-is.

Observations

  • Wouldn't write 100% clean event sourcing — instead, every update would also store a change record containing the previous values
  • Storing previous values goes against ES philosophy, but allows pagination of history records — which is more practical
  • Would use standard SQL storage for projections rather than JSON — this also enables more granular concurrency controls. Marten only offers two extremes: append everything without checks, or crash if anything has changed. No middle ground
  • Events would be stored in the same transaction into another table, then moved to cheaper storage (e.g. Azure Table Storage) via an outbox pattern once SQL size becomes an issue

Astro SSG

Verdict

Would probably go for an SSR framework next time.

Observations

  • Already had places where a router would help — e.g. shared header, footer, and auth status across pages
  • SSG is simpler for deploy setup, but in real circumstances you just pay for the infrastructure and don't worry about it

Witness the progress.

This is a public engineering journal. Follow the repository to see the commits that built this exact page.

View Source Code

Sign In

Don't have an account?

or