DevBrain is a developer learning and quiz platform built for active practice. You register, pick a topic and difficulty, take a focused quiz, review what you got wrong with full explanations, and watch your progress build up over time. Admins manage the question bank directly from the site using a structured JSON import workflow. No external quiz API involved.
- Description
- Current MVP
- Features
- Tech Stack
- Quick Start
- Configuration
- Architecture Overview
- Database and Models
- Question Import Format
- User Flow
- Admin Flow
- Routes
- Frontend Structure
- Testing
- Project Structure
- Architecture Decisions
- Security Notes
- Known Limitations
- What I Learned
- Roadmap
- Author
- License
DevBrain started as my CS50 Final Project -- a basic quiz app that pulled questions from an external API. That version worked but it had real problems. The quiz experience depended on a third-party API staying up. There was no control over what questions users saw. No history, no review, no admin tools. It was a demo, not a product.
This MVP is a completely different thing. The question bank is owned and stored in the database. Admins import questions through a structured JSON workflow, review drafts before publishing, and manage categories, languages, and fast-start presets from dedicated admin pages. Users register, take quizzes by topic and difficulty, get instant feedback on every answer, review their mistakes with explanations, and build a visible history of their progress.
The idea behind DevBrain is that most developer learning is passive. You watch a course, read a doc, follow a tutorial, and feel like you understand something. That feeling does not always hold up when you sit down and have to answer questions without the material in front of you. DevBrain is built around the idea that active testing is part of learning, not separate from it.
The MVP is a Flask monolith with server-rendered Jinja templates. No external quiz API. No separate frontend framework. Questions live in the database and the admin controls them completely.
The current access model:
- Users have to register before taking quizzes
- Free accounts get 5 quiz attempts per rolling week
- Admin accounts have unlimited access
- The first account registered automatically becomes the admin
- Every account after the first is a regular user
Payments are not implemented yet -- the 5-quiz limit is enforced in app logic for now.
Backend
- SQLAlchemy models with Flask-Migrate support
- Database-backed learning tables: topics, languages, presets, questions, answer_options, quiz_attempts, quiz_attempt_answers, question_import_batches
- Legacy quizzes table kept for older summaries
- First registered user automatically becomes admin
- User roles: admin and normal user
- Free user quota enforced at 5 quiz attempts per rolling week
- Quiz generation from owned published questions, not external API
- Fallback question selection so quizzes still fill even when exact topic/difficulty matches are low
- Quiz selection ignores inactive and deprecated language topics, only picks from active categories
- Case-insensitive language matching at query time -- Python, python, and PYTHON all hit the same filter
- Seeded question bank with starter topics, languages, and presets via
flask init-db - PostgreSQL/Supabase-compatible config with
postgres://topostgresql://normalization - Removed stale QuizAPI helper and hardcoded topic source
Question Import
- Admins import questions by pasting JSON or uploading a
.jsonfile at/admin/import - Imported questions are always saved as draft with
source="import"for manual review before publishing - Topics matched by
topic_idor auto-created from atopicname field - Supports options with
is_correctflags or acorrect_optionindex - Import history tracked in the
question_import_batchestable with batch metadata - Full validation on import payload with clear error messages
sample_question_import.jsonincluded in the repo for testing the import flow
Categories and Languages
- 20 active non-language quiz categories
- 15 editable language filters: Python, JavaScript, TypeScript, SQL, PHP, Java, C#, C++, C, Go, Ruby, Rust, Swift, Kotlin, Shell
- Languages are separate from quiz categories and managed independently
- Fast-start quiz presets are database-driven, not hardcoded
- Category delete behavior: empty categories get deleted, categories with linked questions or presets get deactivated instead so existing content is not broken
- Language delete behavior: deletes the filter entry only, existing questions are not affected
User App
- Dashboard as the main logged-in landing page with total quizzes, average score, strongest topic, weakest topic, recent attempts, recommended practice, and quota status
- Full quiz flow: setup, question, results, review
- Saved answer tracking per attempt
- Instant answer feedback after each question
- Final results page with score, percentage, and grade
- Full review page showing your answer, the correct answer, and the explanation
- History page with filters by topic and difficulty and review links per attempt
- Profile page with account details, role, quota status, stats, and password change
- Login required for all quiz features
Admin
- Admin routes in a dedicated blueprint at
app/admin.py, separated from general app routes - Admin dashboard showing question bank stats: total topics, published questions, drafts, import batches
- Question import workflow at
/admin/import-- paste JSON or upload a file - Full question library with create and edit flows
- Category management at
/admin/topicswith delete/deactivate behavior - Language management at
/admin/languageswith delete behavior - Fast-start preset management at
/admin/presets - Question states: draft, published, archived
- Code-reading question support with language and code snippet fields
- Admin sidebar entry only visible to admin users; routes protected server-side
Frontend
- Split CSS architecture: base, layout, components, pages
- Logged-in app shell with desktop sidebar and mobile navigation
- Wide page layouts so quiz, history, profile, and admin screens are not cramped
- Mobile table behavior converts wide tables into readable card layouts
- Dark theme refined to feel more like a real product
- Homepage expanded with both quiz example types (code snippet and concept question), question styles section, learning signals, category strategy, MVP architecture overview, UX breakdown, question bank quality loop, and roadmap direction -- roughly 2-3x the previous marketing content
- About page expanded with project context, architecture breakdown, and MVP proof section
- New CSS styles for quiz examples, category cloud, signal grid, UX grid, and roadmap strips with responsive behavior
Tests
- 36 tests passing across 5 test files covering models, import workflow, services, quiz logic, dashboard stats, quota checks, editable catalogs, delete behavior, case-insensitive language filtering, and admin role behavior
| Layer | Technology | Purpose |
|---|---|---|
| Backend | Python, Flask | Main web application |
| Templates | Jinja2 | Server-rendered pages |
| ORM | SQLAlchemy | Models and queries |
| Migrations | Flask-Migrate, Alembic | Schema changes |
| Local DB | SQLite | Development |
| Production DB | PostgreSQL | Hosted persistence |
| Forms | Flask-WTF, WTForms | Login, register, reset, profile |
| Flask-Mail | Password reset emails | |
| Sessions | Flask sessions | User login state |
| Frontend | HTML, CSS, JavaScript | UI without a build step |
| Server | Gunicorn | WSGI production server |
| Tests | Python unittest | Model and service coverage |
git clone https://github.com/RichardOyelowo/DevBrain.git
cd DevBrainpython -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activatepip install -r requirements.txtcp .env.example .envEdit .env with your local config. See Configuration for all variables.
flask --app run.py db upgrade
flask --app run.py init-dbThe seed command creates starter topics, languages, presets, and published questions so the app is usable right after setup. Without it you will have an empty question bank.
python run.pyVisit http://localhost:5000
The first account you register becomes the admin. Every account after that is a regular user.
| Variable | Required | Description |
|---|---|---|
SECRET_KEY |
Yes | Flask session signing key |
DATABASE_URL |
Yes | SQLAlchemy database URL |
EMAIL |
For password reset | Sender email address |
EMAIL_PASSWORD |
For password reset | SMTP password or app password |
MAIL_SERVER |
For password reset | SMTP server host |
MAIL_PORT |
For password reset | SMTP server port |
REDIS_URL |
Optional | Redis-backed sessions if needed |
FLASK_ENV |
Optional | Set to development locally |
Example local .env:
SECRET_KEY=change-me
DATABASE_URL=sqlite:///instance/devbrain.db
EMAIL=devbrain@example.com
EMAIL_PASSWORD=change-me
MAIL_SERVER=smtp.gmail.com
MAIL_PORT=587
FLASK_ENV=developmentFor production use PostgreSQL. If your host gives you a URL starting with postgres://, the app normalizes it to postgresql:// automatically.
Browser
|
v
Flask routes (main blueprint + admin blueprint)
|
v
Jinja templates + app services
|
v
SQLAlchemy models
|
v
SQLite (local) / PostgreSQL (production)
DevBrain owns its question bank entirely. Nothing depends on an external service being available. Published questions live in the database and are selected by the backend when a user starts a quiz. The admin controls the full content lifecycle from import through draft review to publishing.
| Table | Purpose |
|---|---|
users |
Accounts and roles |
topics |
Quiz categories (20 active) |
languages |
Language filters (15) |
presets |
Fast-start quiz cards (database-driven) |
questions |
The question bank |
answer_options |
Answer choices per question |
quiz_attempts |
Each quiz session |
quiz_attempt_answers |
Each individual answer within a session |
question_import_batches |
Import history and batch metadata |
quizzes |
Legacy table kept for older summaries |
The question model supports topic, difficulty, prompt, explanation, draft/published/archived status, and optional language and code snippet fields for code-reading questions. Each question has multiple answer options with exactly one marked correct.
When a user starts a quiz, DevBrain selects published questions matching the chosen topic and difficulty. If there are not enough exact matches it broadens the pool automatically so the quiz fills the requested count rather than failing. Language matching is case-insensitive so variations in casing never break question selection.
Admins import questions at /admin/import by pasting JSON or uploading a .json file. Imported questions are always saved as draft with source="import" for review before publishing. A sample import file is included at sample_question_import.json.
[
{
"topic": "Python",
"difficulty": "EASY",
"prompt": "What does len(items) return?",
"description": "Assume items is a list.",
"explanation": "len(items) returns the number of elements in the list.",
"language": "python",
"code_snippet": "items = [1, 2, 3]\nprint(len(items))",
"options": [
{ "text": "3", "is_correct": true },
{ "text": "2", "is_correct": false },
{ "text": "1", "is_correct": false },
{ "text": "It raises an error", "is_correct": false }
]
}
]Topics are matched by topic_id or auto-created from the topic name field. Options support both is_correct flags and a correct_option index. Each import run is tracked in question_import_batches with metadata.
Before using the importer on a fresh local setup, run:
flask --app run.py db upgrade- Visit the homepage -- logged-in users are redirected straight to the dashboard
- Register an account or log in
- Dashboard shows progress stats, quota status, recent attempts, and recommended practice
- Open quiz setup, choose topic, difficulty, and how many questions
- Answer each question and get instant feedback after each one
- Final results page shows total score, percentage, and grade
- Review page shows every question with your answer, the correct answer, and the explanation
- History and dashboard update automatically from the saved attempt
The first account registered becomes the admin automatically. No setup command needed. Admins see an extra sidebar entry that regular users never see. Admin routes are in a dedicated blueprint and protected server-side so visiting them without the right role returns a 403.
Admins can:
- View the admin dashboard with question bank health stats
- Import questions via JSON paste or file upload at
/admin/import - Review and publish imported drafts from the question library
- Create and edit questions manually
- Move questions between draft, published, and archived states
- Manage quiz categories at
/admin/topics-- delete empty ones, deactivate ones with linked content - Manage language filters at
/admin/languages-- delete filter entries without affecting existing questions - Manage fast-start presets at
/admin/presets
| Route | Purpose |
|---|---|
GET / |
Homepage -- redirects to dashboard if logged in |
GET /about |
About page |
GET /login |
Login page |
POST /login |
Process login |
GET /register |
Registration page |
POST /register |
Process registration |
GET /forgot_password |
Password reset request |
POST /forgot_password |
Send reset email |
GET /reset_password/<token> |
Reset form |
POST /reset_password/<token> |
Update password |
| Route | Purpose |
|---|---|
GET /dashboard |
Progress dashboard |
GET /quiz |
Quiz setup with categories, languages, presets |
POST /quiz/start |
Validate quota and create attempt |
GET /quiz/<id>/question |
Active quiz question |
POST /quiz/<id>/question |
Submit answer and advance |
GET /quiz/<id>/results |
Final score and grade |
GET /quiz/<id>/review |
Full review with answers and explanations |
GET /history |
Quiz history with filters |
GET /profile |
Account details, quota, stats, password change |
GET /logout |
Clear session |
| Route | Purpose |
|---|---|
GET /admin |
Admin dashboard |
GET /admin/questions |
Full question library |
GET/POST /admin/questions/new |
Create question |
GET/POST /admin/questions/<id>/edit |
Edit question |
GET/POST /admin/topics |
Category management |
POST /admin/topics/<id>/delete |
Delete or deactivate category |
GET/POST /admin/languages |
Language filter management |
POST /admin/languages/<id>/delete |
Delete language filter |
GET/POST /admin/presets |
Fast-start preset management |
GET/POST /admin/import |
Question import workflow |
No npm build step and no frontend framework. HTML, CSS, and a small amount of JavaScript.
app/static/
css/
base.css # Reset, variables, typography
layout.css # App shell, sidebar, mobile nav
components.css # Buttons, cards, badges, tables
pages.css # Page-specific overrides, quiz examples, category cloud, signal grid, roadmap strips
js/
app.js # Lightweight UI behavior
The design is a dark SaaS-style interface. The logged-in app uses a sidebar on desktop and a collapsible mobile nav on smaller screens. Wide tables convert into readable card layouts on mobile. The homepage now has both quiz example types, a question styles section, category and language breakdowns, and several sections explaining the platform, the admin workflow, and the product direction.
Run the full test suite:
SECRET_KEY=test \
DATABASE_URL=sqlite:///:memory: \
EMAIL=test@example.com \
EMAIL_PASSWORD=test \
MAIL_SERVER=localhost \
FLASK_ENV=development \
venv/bin/python -m unittest discover -s testsTest files:
tests/
base.py # Shared test setup and app context
test_user_model.py # User creation, password hashing, role logic, first-user admin
test_topic_question_models.py # Topics, questions, answer options, status transitions, delete/deactivate behavior
test_quiz_attempt_models.py # Attempt creation, answer saving, scoring, grade calculation
test_question_import_model.py # Import batch model, validation, draft creation, source tracking
test_learning_services.py # Question selection, fallback logic, case-insensitive language filtering, quota checks, dashboard stats, editable presets
Last known test run: 36 tests OK
DevBrain/
app/
__init__.py # App factory, extension setup, blueprint registration
admin.py # Admin blueprint -- question bank and catalog management
auth.py # Login, register, password reset
config.py # Config from environment variables
db.py # Database initialization
extensions.py # Flask extensions, login_required, admin_required decorators
forms.py # WTForms definitions
learning.py # Core business logic -- quiz, quota, scoring, import
models.py # SQLAlchemy models
routes.py # Main routes blueprint
seed.py # Starter data for fresh installs
static/
css/
base.css
layout.css
components.css
pages.css
js/
app.js
favicon/
templates/
admin/
import_questions.html
index.html
languages.html
presets.html
question_form.html
questions.html
topics.html
about.html
dashboard.html
history.html
index.html
layout.html
login.html
profile.html
quiz_question.html
quiz_setup.html
register.html
results.html
review.html
migrations/
versions/
20260502_0002_question_import_batches.py
20260502_0003_languages_and_quiz_presets.py
tests/
base.py
test_user_model.py
test_topic_question_models.py
test_quiz_attempt_models.py
test_question_import_model.py
test_learning_services.py
images/
gunicorn.conf.py
requirements.txt
run.py
sample_question_import.json
readme.md
Admin routes moved out of routes.py into a dedicated admin.py blueprint. The browser URLs stayed the same so nothing external broke. This keeps the main app routes focused on the user-facing experience and the admin routes in one place that is easier to reason about and extend.
The AI draft feature was removed from the active app. AI-generated questions were inconsistent in quality and needed the same manual review as any other content anyway. The JSON import workflow replaced it with something the admin fully controls. Questions are predictable, the source is tracked per question, and the review process is consistent regardless of where the content came from.
Deleting a category that has linked questions or presets would break existing quiz history and content references. Empty categories get deleted cleanly. Categories with linked content get deactivated instead so the data stays intact but the category stops appearing in quiz setup.
Language filter entries are separate from questions. Deleting a language filter removes it from the admin catalog but leaves existing questions untouched. This means you can clean up the language list without accidentally removing content.
Language matching at query time is case-insensitive. Python, python, and PYTHON all resolve to the same filter. This prevents quiz selection from breaking because of inconsistent casing in imported questions.
The original version called an external quiz API every time a user started a quiz. Moving to a database-backed question bank fixed the reliability problem and gave the admin full control over content quality.
Rather than returning an error when exact topic and difficulty matches are low, the app broadens the pool to fill the requested count. This keeps the quiz experience smooth while the question bank is still being built out.
No separate admin setup command. The first account registered automatically gets the admin role so the owner can register first and start managing the site immediately.
Dashboard is for understanding your progress and deciding what to practice. Profile is for account settings. Keeping them separate makes both more focused.
- Passwords hashed with Werkzeug
- Password reset uses signed time-limited tokens
- Jinja autoescaping protects all template output
- Admin routes check role server-side -- not just hidden from the sidebar
- Session cookies are HTTP-only
- Secure cookies enabled in production over HTTPS
Still needs before serious production use: rate limiting on auth routes, a thorough CSRF audit across admin forms, and billing enforcement once payments are added.
- Payments not implemented -- the 5-quiz weekly limit is app logic only
- The question bank needs significantly more questions per topic to feel deep
- Dashboard can grow with better charts and longer-term trend analysis
- Redis is optional and not required for the core quiz path
The original version got questions from an API, showed you a quiz, and stopped there. Building the MVP taught me how much more a real product needs -- accounts, persistence, history, review pages, admin tools, role checks, seed data, migrations, and a UI that holds up across screen sizes.
Keeping all admin routes inside the main routes file worked at first but got harder to navigate as the admin surface grew. Moving them into a dedicated blueprint made the codebase easier to read, easier to test, and easier to extend without touching the user-facing routes at all.
A naive delete on a category that has linked questions breaks quiz history. Thinking through when to delete and when to deactivate, and building that behavior explicitly, is the kind of thing that matters in a real product but is easy to skip in a demo.
AI draft generation felt powerful at first but the quality was inconsistent and it still needed the same review as any other content. The JSON import workflow replaced it with something predictable and fully in the admin's control.
Once you have weekly quotas, question selection logic, scoring, grade calculation, delete behavior, and role-based access, those are product rules that need tests. Catching a broken quota check or a bad delete cascade through a test is much better than catching it through a user report.
Short-term
- More questions per topic and difficulty
- Stronger dashboard insights with long-term trend views
- Stripe or similar billing for unlimited plan
Medium-term
- Topic-based recommendations driven by weak area data
- Better admin draft review and bulk edit workflow
Long-term
- Public learning paths
- Team or classroom accounts
- Mobile app wrapper if usage justifies it
Built by Richard Oyelowo as an evolution of my CS50 Final Project.
What started as a single-file Flask quiz app using a third-party API grew into a full product with an owned question bank, structured import workflow, dedicated admin blueprint, account management, and a test suite. The goal was to build something that actually works as a learning tool, not just something that looks like one.
GitHub: github.com/RichardOyelowo Email: richardadebowale.oye@gmail.com
This software is proprietary. You may view and read the source code for personal learning. Deploying, distributing, modifying, or using any part of this software for any purpose without explicit written permission from the author is not allowed.
Built with love for development by Richard Oyelowo
See full terms © All Rights Reserved
