Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ jobs:
test:
name: Test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
defaults:
run:
# The Python backend lives in backend/ since the monorepo split.
working-directory: backend
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -41,6 +45,7 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: backend/requirements*.txt

- name: Set up Node.js
# Node powers the JavaScript code runner; without it the JS runner tests
Expand Down
34 changes: 29 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,34 +26,58 @@ Most interview platforms are puzzle grinders. O(1) Prep puts you in a real inter

## Quick Start

The repository is a monorepo: the Python API lives in **`backend/`**, the web client in **`frontend/`**, and shared infrastructure (`docker-compose.yml`) at the root.

```bash
git clone <repo-url>
cd o1prep
docker compose up -d # start PostgreSQL (from the repo root)

cd backend # the Python API lives here
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
```

Copy `.env.example` to `.env` and set your values (database URL, a secret key, and your OpenAI key):
Copy `backend/.env.example` to `backend/.env` and set your values (database URL, a secret key, and your OpenAI key):

```
DATABASE_URL=postgresql+psycopg://o1prep:o1prep@localhost:5433/o1prep
SECRET_KEY=change-me
OPENAI_API_KEY=sk-your-key-here
```

Start PostgreSQL, create the schema, load the problems, and run:
Create the schema, load the problems, and run — all from `backend/`:

```bash
docker compose up -d # start PostgreSQL
export FLASK_APP=app
flask db upgrade # create tables
python scripts/seed_problems.py # load the problem library
python app.py
```

Open **http://localhost:5000**, create an account, and start practicing.
This serves the API (and the legacy server-rendered UI) on **http://localhost:5000**.

### Frontend (Next.js)

The web client is a separate Next.js app in `frontend/` that talks to the Flask
API. In a second terminal:

```bash
cd frontend
npm install
npm run dev # http://localhost:3000
```

Open **http://localhost:3000**. The dev server proxies `/api/*` to Flask on
`127.0.0.1:5000` (see `frontend/next.config.ts`), so the session cookie stays
same-origin and auth works with no CORS setup. Point it elsewhere with
`API_ORIGIN=http://host:port npm run dev`.

> On macOS, the AirPlay Receiver squats on port 5000 over IPv6 — the proxy
> targets `127.0.0.1` (IPv4) specifically to avoid it. If you change the Flask
> port, update `API_ORIGIN`.

> **Prerequisites:** Python 3.11+, Docker (for PostgreSQL), a paid OpenAI API key, and a modern browser (Chrome/Firefox/Safari). Node.js is optional — install it (or set `NODE_BIN`) to run and test solutions in JavaScript.
> **Prerequisites:** Python 3.11+, Node.js 20+, Docker (for PostgreSQL), a paid OpenAI API key, and a modern browser (Chrome/Firefox/Safari).

---

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
66 changes: 34 additions & 32 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,36 +247,38 @@ User ends interview

```
o1prep/
├── app.py # Flask app factory, index route, blueprint registration
├── config.py # All settings, model names, prompt loading
├── services/ # Business logic
│ ├── ai.py # OpenAI client, streaming, test generation
│ ├── sessions.py # Session file I/O
│ ├── problems.py # DB-backed problem access + YAML seeding
│ ├── db.py # Problem agnostic/per-language split helpers
│ ├── models.py # SQLAlchemy models (Problem, User, Session, ...)
│ ├── extensions.py # db / login_manager / migrate singletons
│ └── code_runner.py # Subprocess code execution
├── routes/ # HTTP layer (Flask Blueprints)
│ ├── auth.py # Register / login / logout
│ ├── sessions.py # Session CRUD, chat, code, transcript endpoints
│ ├── problems.py # Problem list and detail endpoints
│ ├── code.py # Ad-hoc code execution endpoint
│ ├── realtime.py # WebRTC SDP proxy for voice mode
│ └── research.py # Study/tutor chat endpoint
├── migrations/ # Alembic (Flask-Migrate) migrations
├── docker-compose.yml # Local PostgreSQL service
├── scripts/
│ └── seed_problems.py # Seed the database from the problem YAMLs
├── templates/
│ └── index.html # Single-page app shell + CDN library imports
├── static/ # Flask-served app assets
│ ├── style.css
│ ├── favicon.*
│ └── js/ # Frontend modules (9 files, ~2400 lines total)
├── prompts/ # LLM system prompts (6 files)
├── problems/ # 150+ YAML problem definitions (canonical source)
├── tests/ # pytest suite (code runner, problems, DB, schema)
├── docs/ # Documentation and screenshots
└── .env # DATABASE_URL, SECRET_KEY, OpenAI API key (git-ignored)
├── backend/ # Python API (Flask) — run everything from here
│ ├── app.py # Flask app factory, index route, blueprint registration
│ ├── config.py # All settings, model names, prompt loading
│ ├── services/ # Business logic
│ │ ├── ai.py # OpenAI client, streaming, test generation
│ │ ├── sessions.py # Session file I/O
│ │ ├── problems.py # DB-backed problem access + YAML seeding
│ │ ├── db.py # Problem agnostic/per-language split helpers
│ │ ├── models.py # SQLAlchemy models (Problem, User, Session, ...)
│ │ ├── extensions.py # db / login_manager / migrate singletons
│ │ └── code_runner.py # Subprocess code execution
│ ├── routes/ # HTTP layer (Flask Blueprints)
│ │ ├── auth.py # Register / login / logout
│ │ ├── sessions.py # Session CRUD, chat, code, transcript endpoints
│ │ ├── problems.py # Problem list and detail endpoints
│ │ ├── code.py # Ad-hoc code execution endpoint
│ │ ├── realtime.py # WebRTC SDP proxy for voice mode
│ │ └── research.py # Study/tutor chat endpoint
│ ├── migrations/ # Alembic (Flask-Migrate) migrations
│ ├── scripts/
│ │ └── seed_problems.py # Seed the database from the problem YAMLs
│ ├── templates/ # Legacy server-rendered shell (removed once the
│ │ └── index.html # Next.js frontend reaches parity)
│ ├── static/ # Legacy Flask-served app assets (same lifecycle)
│ │ ├── style.css
│ │ ├── favicon.*
│ │ └── js/ # Legacy vanilla-JS modules (~2400 lines total)
│ ├── prompts/ # LLM system prompts (6 files)
│ ├── problems/ # 150+ YAML problem definitions (canonical source)
│ ├── tests/ # pytest suite (code runner, problems, DB, schema)
│ └── .env # DATABASE_URL, SECRET_KEY, OpenAI API key (git-ignored)
├── frontend/ # Next.js + TypeScript web client (consumes the API)
├── docker-compose.yml # Local PostgreSQL service (shared infra)
└── docs/ # Documentation and screenshots
```
41 changes: 41 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
5 changes: 5 additions & 0 deletions frontend/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
<!-- END:nextjs-agent-rules -->
1 change: 1 addition & 0 deletions frontend/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
36 changes: 36 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
18 changes: 18 additions & 0 deletions frontend/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";

const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);

export default eslintConfig;
21 changes: 21 additions & 0 deletions frontend/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { NextConfig } from "next";

// The Flask API runs separately (default :5000). We proxy every /api/* request
// to it so the browser only ever talks to the Next origin — this keeps the
// Flask-Login session cookie same-origin, so auth works with no CORS setup.
// Use 127.0.0.1 (not localhost): on macOS, localhost resolves to ::1 first,
// where the AirPlay Receiver squats on port 5000 — 127.0.0.1 forces IPv4/Flask.
const API_ORIGIN = process.env.API_ORIGIN ?? "http://127.0.0.1:5000";

const nextConfig: NextConfig = {
async rewrites() {
return [
{
source: "/api/:path*",
destination: `${API_ORIGIN}/api/:path*`,
},
];
},
};

export default nextConfig;
Loading
Loading