SB
projects
Live · 2026 Personal

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.

The problem

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.

Highlights

What is technically interesting

01

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.

02

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.

03

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.

04

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).

05

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.

06

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.

Stack

Under the hood

Backend
Java 17 Spring Boot 3.5 Spring Web Spring @Async HandlerInterceptor Lombok
Persistence
PostgreSQL 17 (Neon) Spring Data JPA Hibernate Flyway HikariCP
Frontend
React 18 TypeScript Vite
AI
Gemini 2.5 Flash Lite Vision multi-modal Prompt FR / EN
Tests
JUnit 5 @DataJpaTest @WebMvcTest MockMvc H2 (PostgreSQL mode)
Ops
Docker multi-stage Render (free tier) Cloudflare Pages

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.