AltForge
AI alt-text generator for WCAG compliance. Single + sync batch + async batch (job polling), Postgres audit DB, per-IP rate limiting, aggregated stats. 4-tab UI, 27 tests, free-tier deploy on Render + Cloudflare Pages.
Why I built this
Since June 2025, the European Accessibility Act requires e-commerce sites, banks and public services to provide proper image descriptions for screen readers. Most sites have thousands of legacy images without alt-text, and manual writing simply doesn't scale. AltForge automates the first pass while staying aligned with WCAG best practices (no "image of", descriptive, concise), with an audit and rate-limiting layer ready for multi-tenant production use.
What is technically interesting
Postgres audit DB with an interceptor chain
Every API call is recorded in `request_audits` (Spring Data JPA + Flyway, SHA-256 hashed IP, latency, status, language, model). A HandlerInterceptor measures server-side latency; the controller posts the business metadata via request attributes — clean separation between cross-cutting concerns and business logic.
Per-IP rate limiting without Redis
A second interceptor counts hits for the same IP hash over the last 60 minutes from the audit table, returns 429 + `Retry-After` past the threshold. Interceptor order is set so the 429 itself is audited (`error_code = rate_limited`). Feature-flagged, fails open if the DB is down.
Async pipeline with @Async + ThreadPoolTaskExecutor
`POST /batch/async` returns 202 + UUID immediately; a dedicated worker (1 core / 2 max / queue 10) processes images sequentially, updating `processed_images` after each Gemini call. The frontend polls `GET /api/jobs/{id}` every 2s and animates a progress bar.
Multi-modal Gemini from Spring Boot, no SDK
Typed Spring 6.1 RestClient, image base64-encoded as `inline_data`, system prompts carefully written in FR and EN to enforce WCAG rules (forbid "image of / picture of", ~125 chars, describe before interpreting).
JPQL-driven aggregated stats
`GET /api/stats?hours=N` computes total / success rate / avg latency + breakdowns per language and endpoint through Spring Data derived queries and JPQL `GROUP BY`. Dedicated UI with horizontal bars and a 24h / 7d / 30d window picker.
A test pyramid that runs without Docker in CI
27 JUnit 5 tests: `@DataJpaTest` against H2 in PostgreSQL mode so the real Flyway migrations exercise the repository queries, `@WebMvcTest` + MockMvc with @MockitoBean for controllers, plain unit tests for interceptors. No Testcontainers dependency.
Under the hood
Current status
v2.2 shipped and live on altforge.pages.dev — 4 tabs (single / batch / async / stats). Public repo on GitHub. v3 (auth + billing) is the next step if I turn this into a real public SaaS.