Free AP Practice:学生自建的 SvelteKit AP 刷题应用
5cisummai/freeappractice
学生开发者 Ajay Saravanan 推出 Free AP Practice,一个基于 SvelteKit + Svelte 5 的 AP 练习应用,可生成 AI 选择题并提供应用内辅导、书签和答题历史。
Free AP Practice is a student-built SvelteKit app for generating AP practice questions, tracking progress, and studying with instant feedback. The project is founded and maintained by Ajay Saravanan.
The goal is straightforward: make AP prep feel faster, more personalized, and more accessible for students who do not want to pay for another prep subscription just to get quality practice.
What's included
- Public marketing pages: landing, about, subjects, summer study guide, blog, stats, privacy, terms, and changelog.
- Authenticated app at
/appfor dashboard, practice, progress, question history, resources, and settings. - SEO practice landing pages under
/practice/[...slug](class and unit pages with internal linking). - AI-generated MCQs with an in-app tutor, bookmarks, and attempt history.
- Optional Super membership with personalized MCQ/FRQ tutoring, Coach, and a weekly study plan.
- Public generation stats at
/stats(backed by/api/question/generation-stats). - Better Auth for email/password and Google sign-in (including Google One Tap when configured).
- SvelteKit API routes for questions, signed-in user data, tutoring, and bug reports.
- Markdown blog posts in
src/content/blog/; question content is stored in Neon PostgreSQL for practice, bookmarks, and history.
Tech stack
- SvelteKit + Svelte 5
- TypeScript
- Tailwind CSS
- Neon PostgreSQL + Drizzle ORM (
@neondatabase/serverlessHTTPS driver) - Better Auth for sessions, email flows, and OAuth
- Vercel AI SDK with an OpenAI-compatible API for question generation and tutor responses (OpenAI or LM Studio locally)
- Resend for transactional email
- AWS S3-compatible storage for one-time legacy question imports
- Stripe + Better Auth Stripe plugin for Super subscriptions, Mem0 for optional tutor memory, and Upstash Redis for short-lived AI controls
- Vercel for deployment (
@sveltejs/adapter-vercel)
Getting started
Prerequisites
- Node.js 20+
- Bun
- A Neon PostgreSQL project/branch for the environment you are running
Setup
-
Install dependencies:
bun install
-
Copy the example env file and fill in your values:
cp .env.example .env
-
(Optional) Start local PostgreSQL and Redis services with
compose.yaml:docker compose up -d
The deployed app uses the Neon
DATABASE_URL. The local PostgreSQL service is available atpostgresql://postgres:password@localhost:5432/freeappracticefor SQL inspection and migration work; the runtime must use a Neon branch URL so@neondatabase/serverlesscan send requests over HTTPS. -
Start the dev server:
bun dev
-
Build for production:
bun run build
-
Apply pending schema migrations (when
drizzle/SQL changed):bun run db:apply
Point
DATABASE_URLat the intended Neon branch before running. On push tomainorstaging, CI applies migrations before Vercel deploys. -
Run the unit test suite:
bun test:unit
-
Run the deployed question-generation smoke test:
SMOKE_BASE_URL=https://staging.example.com bun run smoke:question
This checks the public practice page and the same
POST /api/questionpath used by the browser. It retries temporary pool-warming responses and verifies that the returned question has a prompt, four A–D choices, a correct answer, and a question ID. SetSMOKE_CLASSandSMOKE_UNITwhen checking another supported course or unit.
Useful scripts
| Command | Purpose |
|---|---|
bun check |
Type-check with svelte-check |
bun test:unit |
Run Vitest unit tests |
bun run db:apply |
Apply committed Drizzle migrations to Neon |
bun lint / bun format |
ESLint and Prettier |
Environment variables
Copy .env.example to .env. Required for a working local setup:
| Variable | Purpose |
|---|---|
DATABASE_URL |
Neon PostgreSQL connection string used by Drizzle over HTTPS |
BETTER_AUTH_SECRET |
Session signing secret (min 32 chars; generate with openssl rand -base64 32) |
BETTER_AUTH_URL |
App base URL for auth callbacks (e.g. http://localhost:5173) |
OPEN_AI_KEY |
API key for the configured provider (any value works for local LM Studio) |
OPENAI_BASE_URL |
OpenAI-compatible API base URL (defaults to https://api.openai.com/v1) |
Model choices for every AI use case are plain exported constants in
src/lib/ai/ai-models-config.ts.
Commonly needed for full functionality:
| Variable | Purpose |
|---|---|
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET |
Google OAuth |
PUBLIC_GOOGLE_CLIENT_ID |
Google One Tap on the client |
RESEND_API_KEY / RESEND_FROM |
Transactional email |
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_REGION / AWS_S3_BUCKET |
Optional legacy S3 import tooling |
AWS_SESSION_TOKEN |
Optional temporary/assumed-role credentials |
PUBLIC_BASE_URL |
Canonical site URL |
GITHUB_BUG_REPORT_TOKEN |
GitHub Issues API for in-app bug reports |
CRON_SECRET |
Secures Vercel cron routes (Authorization: Bearer …); required in production for scheduled jobs |
Super launch configuration
Super is disabled until Stripe credentials and both price IDs are present. Configure STRIPE_SECRET_KEY,
STRIPE_WEBHOOK_SECRET, STRIPE_SUPER_MONTHLY_PRICE_ID, STRIPE_SUPER_ANNUAL_PRICE_ID,
DATABASE_URL, OPEN_AI_KEY, KV_REST_API_URL, KV_REST_API_TOKEN, and
REDIS_IDENTIFIER_SECRET in Vercel before enabling the Super Flags. Enable the pgvector
extension and apply the Drizzle migrations to each Neon database before enabling memory.
In Stripe, configure the Customer Portal to allow cancellation at period end, restoration before the end of
the current period, and switching between the two Super prices at the next renewal. Point the Stripe webhook
to Better Auth's /api/auth/stripe/webhook route and enable at least checkout.session.completed,
customer.subscription.created, customer.subscription.updated, customer.subscription.deleted,
invoice.paid, invoice.payment_failed, invoice.payment_action_required, and
invoice.finalization_failed. Configure Stripe Tax registrations before enabling automatic tax. The app
enables automatic tax and does not enable promotion codes.
Tutor memory uses the Mem0 OSS SDK with OpenAI extraction/embeddings and an Upstash Vector index. The app
keeps Development, Preview, and Production memories isolated inside the shared index with deployment-scoped
namespaces and metadata. Connect the Upstash Vector integration to all three Vercel environments and pull the variables locally
with vercel env pull .env.development.local --environment=development when setting up a new machine.
Upstash Redis is limited to fast, disposable control-plane data: rate limits, monthly AI-turn reservations,
single-flight locks, idempotency keys, and 30-minute Coach approvals. Upstash Vector stores optional tutor
memories; Neon PostgreSQL and Stripe remain the other durable stores. Question-selection and grading logic do not
use Redis. For local Redis testing, run an
Upstash-compatible Serverless Redis HTTP proxy in front of a local Redis server and point the same
KV_REST_API_URL and KV_REST_API_TOKEN variables at that proxy.
Pool fill targets, leases, and daily LLM budget are coded in src/lib/question-bank/pool-constants.ts (not env). See docs/question-pool-runbook.md.
Coach images use a connected private Vercel Blob store in deployments. Locally, run
bun run storage:local to start RustFS and create its private S3 bucket, then add the
COACH_IMAGE_S3_* values from .env.example to .env.local. The console is at
http://127.0.0.1:9001. Enable the coach-images Vercel flag for Development to use
the composer. Uploads accept one PNG, JPEG, or WebP up to 2 MB, with limits of 8192
pixels per side and 16 megapixels. Images are normalized to WebP without metadata.
Each chat permits 50 images and each account 500; deleting chats frees storage.
Chat/account deletion preserves attachment paths for retryable cleanup in the Super
maintenance job. Failed, unattached uploads are eligible for cleanup after one hour.
Local AI with LM Studio
To run question generation and tutoring against a local model instead of OpenAI:
-
Start LM Studio and load a model with the local server enabled (default
http://localhost:1234/v1). -
Set in
.env:OPENAI_BASE_URL=http://localhost:1234/v1 OPEN_AI_KEY=lm-studio
Set the model identifier shown in LM Studio in
src/lib/ai/ai-models-config.ts.
API overview
Routes live under src/routes/api as SvelteKit server endpoints.
Authentication (Better Auth)
All auth flows are handled by Better Auth at /api/auth/* (sign-up, sign-in, sign-out, email verification, password reset, Google OAuth, session management, account deletion, and email change). Use the Better Auth client in src/lib/auth/client.ts from the browser; do not call legacy /api/auth/register or /api/auth/login endpoints — those were removed in v1.4.1.
Signed-in user data (/api/me/*)
These routes require an active Better Auth session:
| Method | Route | Description |
|---|---|---|
GET |
/api/me/stats |
User practice statistics |
GET |
/api/me/progress |
Progress by subject/unit |
GET |
/api/me/history |
Paginated MCQ attempt history. Query params: page (default 1), limit (default 50, max 200), optional apClass |
POST |
/api/me/record-attempt |
Record an answer attempt |
GET |
/api/me/bookmarks |
List bookmarked questions |
POST |
/api/me/bookmarks |
Add or remove a bookmark |
Questions
| Method | Route | Description |
|---|---|---|
POST |
/api/question |
Select an active AP question from the pool |
GET |
/api/question/generation-stats |
Public read-only generation counters |
Other
| Method | Route | Description |
|---|---|---|
POST |
/api/tutor/chat, /api/tutor/greeting |
AI tutor assistant |
POST |
/api/bug-report |
Submit bug reports as GitHub Issues (rate-limited per IP) |
GET |
/health |
Health check |
Optional Vercel Analytics and Speed Insights load only after a user opts in inside the app.
Bug reports
The in-app bug report form creates GitHub Issues in this repository via the GitHub Issues API.
- Reports are labeled automatically with
bugand a severity label. - A server-side per-IP rate limit reduces spam.
- Set
GITHUB_BUG_REPORT_TOKENwith Issues: Read & Write access scoped to this repository.
Deployment
Deploy to Vercel as a SvelteKit app:
- Connect the repository to Vercel.
- Set production environment variables (at minimum
DATABASE_URL,BETTER_AUTH_SECRET,BETTER_AUTH_URL, andOPEN_AI_KEY). Runtime only — the Vercel build does not need database access. - Add
DATABASE_URL(andSTAGING_DATABASE_URLif you use thestagingbranch) as GitHub Actions secrets. Pushes tomain/stagingrunbun run db:applyin CI before Vercel bundles the app. - Vercel runs
bun run build(vite build) via the default SvelteKit integration.
BETTER_AUTH_URL and PUBLIC_BASE_URL must match your production domain. If you change hosting, update the SvelteKit adapter and these notes together.
Branding & usage
The Freeappractice name, logo, and brand identity are proprietary and are exclusively reserved for Ajay Saravanan. The source code is open, but the brand is not.
What you are welcome to do
- Star or like the repository
- Fork the repository and use the code as a starting point for your own project
- Open pull requests, report bugs, and contribute improvements back to this repository
What is not permitted
- Launching any public site, app, or service under the name Freeappractice, Free AP Practice, or any confusingly similar name or branding
- Creating a derivative product that implies it is Freeappractice or a related official product
- Claiming any affiliation, endorsement, or partnership with Freeappractice or Ajay Saravanan without explicit written permission
If you fork the code to build something of your own, give it a completely different name and make it clear that it is an independent project unrelated to Freeappractice.
Notes
- Question generation and grading are API-driven; the UI is fully SvelteKit.
- The landing page routes authenticated users into
/app. - The blog and summer study guide are meant to bridge reading content with active practice.
- Practice landing pages and the stats page are public; signed-in features live under
/app.
来源:GitHub · github.com