Web application

HokieHub

Peer-to-peer marketplace for Virginia Tech students. Bidding, seller-declared faults, and a paid AI price check grounded in real comparable listings; restricted to verified @vt.edu accounts

What I built

  • Rebuilt a marketplace backend in Java 21 and Spring Boot: JPA over five tables, boundary validation, RFC 7807 errors
  • Cut a 20-row listing page from 62 SQL statements to 2 with JPA entity graphs, plus indexes the schema never defined
  • Wrote 78 Mockito and Testcontainers tests over real Flyway migrations, catching eleven defects
  • Containerized API, PostgreSQL and Redis with multi-stage builds, versioned Flyway migrations and CI on every push
  • Built a paid AI price check over listing photographs, refusing an estimate unless search returned real comparables
  • Split an LLM valuation that answered from memory once given a schema into grounded retrieval and tool-less structuring
  • Built the marketplace on Next.js 15 and Postgres with @vt.edu verification and a 12-by-20 category tree

How the numbers were measured

Cut a 20-row listing page from 62 SQL statements to 2 with JPA entity graphs, plus indexes the schema never defined
Both numbers are assertions in ListingQueryCountIT, which reads Hibernate's own getPrepareStatementCount() while rendering the same page each way against a PostgreSQL Testcontainer. 62 = the page query + pagination's count query + three loads per row (seller, category, and the service detail, whose FetchType.LAZY Hibernate cannot honour on the inverse side of a one-to-one). The earlier figure of 41 was arithmetic, not a measurement, and was wrong.
Wrote 78 Mockito and Testcontainers tests over real Flyway migrations, catching eleven defects
`mvn verify` on 2026-08-29: 15 unit tests, 63 integration tests, 0 failures. The same command runs on every push and is green in GitHub Actions.
Built the marketplace on Next.js 15 and Postgres with @vt.edu verification and a 12-by-20 category tree
Verified 2026-09-09 against the live API (api-production-73e8.up.railway.app/api/categories): 12 top-level, 20 children, 32 total. The parent-filter behaviour was verified separately. /api/listings?categoryId=2 (Electronics) returns listings whose own categories are "Audio & Headphones" and "Laptops & Computers", both children of Electronics

Built with

JavaSpring BootJPAHibernatePostgreSQLFlywayREST API designDockerData ModellingSpring SecurityOAuth 2.0JWTJWKSRedisCachingRate LimitingTestcontainersJUnitMockitoMavenGitHub ActionsNext.jsReactTypeScriptSupabaseTailwindRailwayDocker ComposeGeminiMultimodal LLMRetrieval GroundingFile UploadPrompt DesignSpringdoc OpenapiSwagger UiShadcn UiEslintPrettierPg TrgmGin IndexGhcrVercelRfc7807Bean ValidationLettuceHikaricpgitleaksSvgNext ImageServer Actions

Why this stack

spring-security/oauth2/jwt. The resource server validates Supabase HS256 tokens. Pg-trgm/gin-index. Trigram indexes backing keyword search, because an unanchored LIKE cannot use a B-tree. Ghcr. Railway's builder failed on every source and region, so the image is built in GitHub Actions, published to the container registry, and Railway runs the tag. Rfc7807. Every error response is application/problem+json. Testcontainers. Real PostgreSQL, real Flyway migrations, and a test that counts Hibernate's own statements.