SkillEx
An Advanced OOP course project taken past the coursework brief. The interesting constraint is that there is no currency: a match is only useful when two people's offers and wants genuinely complement each other, which turns matching into the core engineering problem rather than a feature bolted on later.

The Problem
Skill learning is gated by cost, while plenty of people hold knowledge worth trading. Keyword matching is not good enough to pair them — someone offering "React" and someone wanting "frontend development" never meet, even though they should.
The Solution
Skills are embedded through the Gemini Embedding API and matched by semantic similarity, so related phrasings converge. Because a hosted embedding service is a hard dependency for a course project, the backend falls back to local embeddings whenever no API key is present — the feature degrades in quality rather than disappearing.
System Architecture
A monorepo with clean seams. The frontend is organised as feature-first vertical slices, and all network access funnels through a single ApiClient singleton, so JWT attachment and error shape live in one place. The backend keeps controllers thin and routes work through service interfaces to implementations, with Flyway owning the schema as versioned SQL.
Key Features
Innovative solutions developed to solve complex technical challenges.
Semantic skill matching
Gemini embeddings place offered and wanted skills in the same vector space, so "React" and "frontend development" match on meaning rather than on shared characters.
Graceful embedding fallback
With EMBEDDING_PROVIDER set to api but no key present, the backend silently switches to local embeddings instead of failing the request.
Single API boundary
An ApiClient singleton wraps fetch, attaches the JWT, and normalises every response into ApiResponse<T> — so success, error, and message handling are identical across features.
Versioned schema with Flyway
Migrations run as ordered SQL on startup, so a fresh clone reaches the current schema without anyone running statements by hand.
Feature-first frontend slices
Each domain owns its components, services, and types together, so a feature can be read or removed without tracing through shared folders.
Challenges & Breakthroughs
Challenge: A hosted embedding API is a single point of failure for the one feature the product is built around.
Growth: Made the provider a configuration choice with an automatic local fallback rather than a hard dependency. Designing the degraded path first meant the feature had a defined behaviour without a key, instead of an exception.
Challenge: Getting a Spring Boot backend and a Vite frontend running together locally involves MySQL, ports, and migration order.
Growth: Automated it — a pre-start script detects and starts MySQL, waits for port 3306, and ensures the database exists before the backend boots. Setup friction is a real cost on a team project; removing it is worth the scripting.
Challenge: Thin controllers are easy to describe and easy to erode once deadlines arrive.
Growth: Keeping the service as an interface with a separate implementation made the boundary structural rather than a convention. It is harder to leak business logic into a controller when doing so means bypassing a type you would have to go around deliberately.
Results & Impact
Next Project
ConvoVerse