Full-stack financial literacy learning platform with AI-powered lessons, authentication, and progress tracking.
Stack: Next.js 15 + FastAPI + SQLModel + SQLite + Google Gemini AI
- Login/Signup - Username or email + password authentication
- Password Reset - Email-based password recovery with token expiration
- Session Management - Database-backed sessions with 7-day expiration
- Guest Mode - Try the platform without creating an account (no progress saving)
- Google OAuth - Coming soon (placeholder implemented)
- AI-Generated Lessons - Dynamic financial literacy lessons powered by Google Gemini
- Interactive Quizzes - Multiple choice, fill-in-blank, and free-response questions
- AI Coach - Chat with an AI financial advisor for help
- Progress Tracking - XP, streaks, and lesson completion tracking
- Gamification - Duolingo-style UI with rewards and celebrations
- User Profiles - Username, email, XP, streak, last login
- Session Storage - Persistent sessions across backend restarts
- Progress Records - Per-user progress tracking with category-based progression
- Node.js 18+ and npm
- Python 3.10+
- Google AI Studio API key (for Gemini)
- Railway account (for backend deploy)
- Vercel account (for frontend deploy)
frontend/ # Next.js 15 (App Router, TypeScript, TailwindCSS)
├── src/
│ ├── app/
│ │ ├── page.tsx # Main learning platform
│ │ ├── login/page.tsx # Login/Signup page
│ │ ├── reset-password/ # Password reset request
│ │ └── confirm-reset/ # Password reset confirmation
│ └── lib/
│ └── api.ts # Backend API client
backend/ # FastAPI + SQLModel + SQLite
├── app/
│ ├── main.py # FastAPI app entry point
│ ├── models.py # Database models (User, Session, etc.)
│ ├── schemas.py # Pydantic request/response schemas
│ ├── db.py # Database connection
│ └── routers/
│ ├── auth.py # Authentication endpoints
│ ├── lessons.py # Lesson generation & evaluation
│ ├── chat.py # AI coach chatbot
│ └── progress.py # User progress tracking
├── app.db # SQLite database (gitignored)
└── requirements.txt # Python dependencies
docs/ # Documentation
├── BRANCHING.md # Git workflow
├── AUTH_IMPLEMENTATION.md # Authentication system docs
└── PASSWORD_RESET_AND_SESSIONS.md # Password reset & session management
DATABASE_URL=sqlite:///./app.db
GOOGLE_API_KEY=your_google_ai_studio_api_key_here
ALLOWED_ORIGINS=http://localhost:3000NEXT_PUBLIC_API_URL=http://localhost:8000Note:
- Get your Google AI Studio API key from: https://makersuite.google.com/app/apikey
- Never commit
.envfiles to git - Frontend only needs the backend URL; all AI calls go through the backend
cd backend && .venv/bin/python -m uvicorn app.main:app --reload --port 8000 & cd ../frontend && npm run devThis starts both backend (port 8000) and frontend (port 3000) in one command.
cd backend
python3 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # Create and fill GOOGLE_API_KEY
uvicorn app.main:app --reload --port 8000Endpoints:
- Health:
GET http://localhost:8000/health - API Docs:
http://localhost:8000/docs - Auth:
/auth/register,/auth/login,/auth/logout - Lessons:
/lessons/generate,/lessons/evaluate_answers - Chat:
/chat - Progress:
/progress/{username}
cd frontend
npm install
cp .env.example .env.local # Set NEXT_PUBLIC_API_URL
npm run dev # http://localhost:3000Routes:
- Home/Login:
http://localhost:3000(redirects to/loginif not authenticated) - Login/Signup:
http://localhost:3000/login - Password Reset:
http://localhost:3000/reset-password - Confirm Reset:
http://localhost:3000/confirm-reset
The SQLite database (backend/app.db) is auto-created on first run with these tables:
- user - User accounts (username, email, password_hash, xp, streak, completed_lessons)
- session - Auth sessions (token, user_id, expires_at)
- passwordresettoken - Password reset tokens (token, email, expires_at)
- xpevent - XP change history for daily tracking
- llmcacherecord - AI response caching
View database:
sqlite3 backend/app.db ".tables"
sqlite3 backend/app.db "SELECT * FROM user;"| Endpoint | Method | Description |
|---|---|---|
/auth/register |
POST | Create new account (username, email, password) |
/auth/login |
POST | Login with username/email + password |
/auth/logout |
POST | Invalidate session token |
/auth/me |
GET | Get current user info (requires token) |
/auth/guest |
POST | Enable guest mode |
/auth/password-reset/request |
POST | Request password reset token |
/auth/password-reset/confirm |
POST | Reset password with token |
| Endpoint | Method | Description |
|---|---|---|
/lessons/generate |
POST | Generate AI lesson (category, level, difficulty) |
/lessons/evaluate_answers |
POST | Evaluate quiz answers |
/lessons/check_free |
POST | Check free-response answer with AI |
/chat |
POST | Chat with AI financial coach |
/progress/{username} |
GET | Get user progress (XP, streak) |
/progress/{username} |
POST | Update user progress |
# Register
curl -X POST http://localhost:8000/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"testuser","email":"test@example.com","password":"password123"}'
# Response: {"user_id":1,"username":"testuser","email":"test@example.com",...,"token":"abc123..."}
# Login (with username or email)
curl -X POST http://localhost:8000/auth/login \
-H "Content-Type: application/json" \
-d '{"username_or_email":"testuser","password":"password123"}'
# Get user info
curl "http://localhost:8000/auth/me?token=abc123..."- Visit
localhost:3000→ Redirects to/login - Click "Sign Up" tab
- Enter username, email, password → Submit
- Automatically logged in → Redirected to main app
- Start learning! XP and streak save to database
- Visit
localhost:3000→ Redirects to/login - Enter username/email + password → Login
- Previous XP and streak loaded from database
- Continue where you left off
- Visit
localhost:3000/login - Click "Continue as Guest"
- Access full platform features
- Progress NOT saved (resets on refresh)
- Click "Forgot Password?" on login page
- Enter email → Receive reset token (displayed on screen in dev mode)
- Go to
/confirm-reset→ Enter token + new password - All sessions invalidated → Must login with new password
- Create a new service from the
backend/folder (GitHub repo) - Add a Persistent Volume mounted at
/data - Set environment variables:
DATABASE_URL=sqlite:////data/app.db GOOGLE_API_KEY=your_key_here ALLOWED_ORIGINS=https://your-vercel-domain.vercel.app - Start command:
uvicorn app.main:app --host 0.0.0.0 --port $PORT - Note the Railway service URL (e.g.,
https://your-app.railway.app)
- Import the
frontend/project from GitHub - In Project Settings → Environment Variables:
NEXT_PUBLIC_API_URL=https://your-railway-service-url - Deploy
- Update Railway
ALLOWED_ORIGINSwith your Vercel URL
- Test signup/login on production
- Verify AI lesson generation works
- Check that XP/streak persists after refresh
- Test password reset flow
- Confirm sessions persist across backend restarts
- Enable HTTPS (automatic on Railway/Vercel)
See docs/BRANCHING.md for full details.
Branch naming:
feat/frontend-*- Frontend featuresfeat/backend-*- Backend featuresfix/*- Bug fixesdocs/*- Documentation updates
Workflow:
- Create feature branch from
main - Make changes and commit
- Open PR with description
- Get 1+ review approval
- Squash merge to
main
# Test authentication
curl -X POST http://localhost:8000/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"test","email":"test@test.com","password":"test123"}'
# Test lesson generation
curl -X POST http://localhost:8000/lessons/generate \
-H "Content-Type: application/json" \
-d '{"category":"Budgeting Basics","level":1,"num_questions":3,"difficulty":"beginner"}'
# View database
sqlite3 backend/app.db "SELECT username, xp, streak FROM user;"- Framework: Next.js 15.5.4 (App Router)
- Language: TypeScript
- Styling: TailwindCSS
- State Management: React hooks (useState, useEffect)
- Auth: localStorage + session tokens
- Icons: Lucide React
- Framework: FastAPI
- ORM: SQLModel
- Database: SQLite (development) / PostgreSQL (production ready)
- AI: Google Gemini 1.5 Pro
- Authentication: Session-based with database storage
- Password Hashing: SHA-256 (upgrade to bcrypt recommended for production)
-- Users table
CREATE TABLE user (
id INTEGER PRIMARY KEY,
email VARCHAR NOT NULL UNIQUE,
username VARCHAR NOT NULL UNIQUE,
password_hash VARCHAR,
google_id VARCHAR UNIQUE,
display_name VARCHAR,
xp INTEGER DEFAULT 0,
streak INTEGER DEFAULT 0,
created_at DATETIME,
last_login DATETIME
);
-- Sessions table
CREATE TABLE session (
id INTEGER PRIMARY KEY,
token VARCHAR NOT NULL UNIQUE,
user_id INTEGER NOT NULL,
created_at DATETIME,
expires_at DATETIME,
last_activity DATETIME
);
-- Password reset tokens
CREATE TABLE passwordresettoken (
id INTEGER PRIMARY KEY,
token VARCHAR NOT NULL UNIQUE,
user_id INTEGER NOT NULL,
email VARCHAR,
created_at DATETIME,
expires_at DATETIME,
used BOOLEAN DEFAULT FALSE
);- Session Duration: 7 days
- Storage: Database (persists across restarts)
- Token Format: 32-byte URL-safe random string
- Token Duration: 1 hour
- Single Use: Tokens invalidated after use
- Email: Currently displays token on screen (dev mode)
- For production: Integrate SendGrid/AWS SES for email delivery
- Model: gemini-1.5-pro
- Caching: LLM responses cached in database
- Rate Limiting: None (add for production)
# Check if port 8000 is in use
lsof -ti:8000 | xargs kill -9
# Recreate virtual environment
cd backend
rm -rf .venv
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtcd frontend
rm -rf node_modules package-lock.json
npm install
npm run dev# Delete and recreate database
rm backend/app.db
# Restart backend - tables will auto-createMake sure components using localStorage have client-side checks:
const [isMounted, setIsMounted] = useState(false);
useEffect(() => setIsMounted(true), []);
if (!isMounted) return <div>Loading...</div>;docs/BRANCHING.md- Git workflow and branching strategydocs/AUTH_IMPLEMENTATION.md- Complete authentication system documentationdocs/PASSWORD_RESET_AND_SESSIONS.md- Password reset and session management detailsdocs/SETUP_COMPLETE.md- Initial setup guide
- ✅ Session-based authentication
- ✅ Password hashing (SHA-256)
- ✅ CORS configuration
- ✅ Token expiration (7 days)
- ✅ Password reset tokens (1 hour)
- Upgrade to bcrypt/Argon2 for password hashing
- Add rate limiting on auth endpoints
- Implement CSRF protection
- Add email verification on signup
- Enable HTTPS only (automatic on Railway/Vercel)
- Add security headers (helmet.js equivalent)
- Implement Google OAuth (placeholder ready)
- Add 2FA option
- Database: Migrate to PostgreSQL for production scale
- Add Redis for session storage (optional, for scale)
- Fork the repository
- Create your feature branch (
git checkout -b feat/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feat/amazing-feature) - Open a Pull Request
- Keep the Gemini API key only on the backend. Frontend never calls Gemini directly.
- SQLite file persists on Railway via the attached volume at
/data. - Sessions persist across backend restarts (stored in database, not in-memory)
- Guest mode progress doesn't save (intended behavior)
- Email is required for signup (needed for password reset)
MIT