Case study · Study platform
PanExams Hub
Every Cyprus exam paper in one place, with AI flashcards and practice tests on top. Built mobile-first, because revision happens on a phone.
The problem
Past papers are the single most useful revision material a Cypriot student has, and they are scattered across ministry pages, school sites and group chats as loose PDFs.
Finding the paper is half the work. The other half is that a PDF is a dead end: you can read it, but you cannot practise against it, track what you keep getting wrong, or revise it in the ten minutes between classes.
The goal
Collect the papers in one searchable place, then turn them into something you can actually study with. And do it on a phone screen first, because that is where revision happens, on a bus, at a desk, late.
There was a second goal that shaped the architecture: the project had to pay for itself. Hosting, storage and AI calls all cost money per user, so a free-only product would have died at the first bill.
What I built
- A paper library aggregating Cyprus exam papers and official PDFs in one browsable place.
- AI-generated flashcards built from the material, so revision does not stop at reading.
- Quizzes and practice tests for working against past papers rather than just opening them.
- Stripe subscriptions for the premium tier, with the free tier kept genuinely usable.
- Firebase Auth with Firestore security rules enforcing what each tier can read.
- A policy-safe AdSense loader for the free tier.
- Mobile-first throughout — the phone layout is the design, not a fallback.
Technical decisions
Three choices, and what each one cost.
The paywall lives in Firestore rules, not in the UI
Hiding a premium button is not a paywall. Anything the client can request, a determined student can request directly, so entitlement is checked server-side in security rules: the subscription state decides what documents are readable, and the interface merely reflects that.
Cost: the rules become real logic that has to be reasoned about and kept in step with Stripe's view of who has paid. Two systems now hold one truth.
Stripe owns billing, Firestore owns access
Payment state is Stripe's problem, and re-implementing cards, renewals, failed charges and refunds would have been the whole project. Stripe stays the source of truth for money; Firestore holds only the derived answer to "does this account have access right now". The sync between them runs through Firebase's Stripe extension rather than a webhook I maintain myself.
Cost: I did not design the schema the subscription data lands in, so the security rules have to be written against someone else's shape. When the sync misbehaves, the debugging happens in a component I did not write — which is the trade every managed integration asks you to make.
An ad loader written around the policy, not the revenue
AdSense on a site used by students is an account-suspension risk before it is an income stream: ads must not load on the wrong pages, near the wrong content, or before consent. The loader is deliberately conservative about where and when it runs.
Cost: fewer impressions than a naive integration would deliver, in exchange for not losing the account.
What was hard
AdSense rejected the site. That is the part of this project worth telling, because it is the part that changed how I build.
A rejection is an unhelpful kind of feedback: you get a policy category, not a line number. What it forced was a rebuild of the ad loader from the other direction — instead of placing ads where they would earn most and checking compliance afterwards, placement starts from what is allowed and stops there. Ads stopped being something bolted on at the end and became a constraint on how pages get built.
Underneath it sits a question any aggregator has to answer honestly: a platform assembled out of other people's PDFs has to add enough of its own value to be a product rather than a wrapper. The flashcards, the quizzes and the practice tests are not decoration on top of the library — they are the argument that the library is worth visiting rather than downloading from.
The rest was the tension between the two tiers. The free tier wants everything cached, static and cheap; the paid tier wants everything checked per request against who you are. Getting both out of the same Firestore structure, without either leaking premium content or making the free experience feel broken, took more iterations than the features did.
AI generation added a cost dimension I had not designed for at first. Every flashcard set is a paid API call, so generation has to be cached and reused rather than re-run whenever someone opens a page.
Where it stands
PanExams Hub is live at panexams-hub.com, with the library, the AI flashcards, the practice tests and the subscription tier all in production.
It is the second product I have taken from an idea to something strangers pay for, and the one where the interesting engineering is almost entirely in the boundaries: who may read what, who has paid, and what happens when those two answers disagree.
Have a project in mind?
I take on client websites, web apps, and product builds across Cyprus and Greece.
Start a conversation