Skip to content

Database Setup

acodeaday uses PostgreSQL for storing problems, user progress, submissions, and test cases. We recommend using Supabase, which provides a managed PostgreSQL database with additional features.

Database Overview

The application requires five core tables:

  1. problems - Problem metadata (title, description, difficulty, etc.)
  2. problem_languages - Language-specific code (starter code, solutions, function signatures)
  3. test_cases - Test inputs and expected outputs
  4. user_progress - Spaced repetition tracking (times solved, next review date, mastery)
  5. submissions - Submission history (code, results, runtime)

Supabase provides a free PostgreSQL database with 500 MB storage and unlimited API requests.

1. Create Supabase Account

  1. Go to supabase.com
  2. Sign up for a free account
  3. Click "New Project"

2. Create Project

Fill in project details:

  • Name: acodeaday
  • Database Password: Choose a strong password (save this!)
  • Region: Choose closest to your location
  • Pricing Plan: Free

Wait 2-3 minutes for project initialization.

3. Get Database Credentials

Connection String

  1. Go to Settings > Database
  2. Scroll to "Connection string"
  3. Copy the URI format
  4. Replace [YOUR-PASSWORD] with your database password

Example:

postgresql://postgres:your-password@db.abc123xyz.supabase.co:5432/postgres

Convert to async format for SQLAlchemy:

postgresql+asyncpg://postgres:your-password@db.abc123xyz.supabase.co:5432/postgres

API Credentials

  1. Go to Settings > API
  2. Copy:
    • Project URL (e.g., https://abc123xyz.supabase.co)
    • anon/public key (starts with eyJ...)

4. Configure Backend .env

Update backend/.env:

bash
DATABASE_URL=postgresql+asyncpg://postgres:YOUR_PASSWORD@db.YOUR_PROJECT_ID.supabase.co:5432/postgres
SUPABASE_URL=https://YOUR_PROJECT_ID.supabase.co
SUPABASE_KEY=YOUR_ANON_KEY

5. Allow Your IP Address

If connecting from your local machine:

  1. Go to Settings > Database
  2. Scroll to "Connection Pooling"
  3. Add your IP to the allowlist (or disable IP restrictions for development)

6. Run Migrations

bash
cd backend
uv run alembic upgrade head

This creates all required tables in your Supabase database.

7. Verify Tables

  1. In Supabase dashboard, go to Table Editor
  2. You should see: problems, problem_languages, test_cases, user_progress, submissions

Option 2: Local Supabase

For offline development or full control, run Supabase locally.

1. Install Supabase CLI

macOS:

bash
brew install supabase/tap/supabase

Windows/Linux:

bash
npm install -g supabase

2. Initialize Supabase

bash
cd acodeaday
supabase init

This creates a supabase/ directory with configuration.

3. Start Local Supabase

bash
supabase start

This starts:

  • PostgreSQL on http://localhost:54322
  • Studio (web UI) on http://localhost:54323
  • API on http://localhost:54321

First run takes ~5 minutes to download Docker images.

4. Get Local Credentials

After startup, the CLI displays credentials:

API URL: http://localhost:54321
DB URL: postgresql://postgres:postgres@localhost:54322/postgres
Studio URL: http://localhost:54323
anon key: eyJ...
service_role key: eyJ...

5. Configure Backend .env

bash
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:54322/postgres
SUPABASE_URL=http://localhost:54321
SUPABASE_KEY=eyJ...  # Use the anon key from CLI output

6. Run Migrations

bash
cd backend
uv run alembic upgrade head

7. Access Supabase Studio

Open http://localhost:54323 to view tables and data in the web UI.

Option 3: Plain PostgreSQL

If you prefer plain PostgreSQL without Supabase:

1. Install PostgreSQL

macOS:

bash
brew install postgresql@15
brew services start postgresql@15

Ubuntu/Debian:

bash
sudo apt install postgresql postgresql-contrib
sudo systemctl start postgresql

Windows: Download from postgresql.org

2. Create Database

bash
# Create database
psql postgres
CREATE DATABASE acodeaday;
\q

3. Configure Backend .env

bash
DATABASE_URL=postgresql+asyncpg://postgres:password@localhost:5432/acodeaday

Note: Without Supabase, authentication features won't work. You'll need to implement custom auth or disable auth in the backend.

4. Run Migrations

bash
cd backend
uv run alembic upgrade head

Database Migrations

Run Migrations

Apply all pending migrations:

bash
uv run alembic upgrade head

Check Current Version

bash
uv run alembic current

View Migration History

bash
uv run alembic history

Rollback Migrations

bash
# Rollback one migration
uv run alembic downgrade -1

# Rollback all migrations (WARNING: deletes all data)
uv run alembic downgrade base

Create New Migration

After modifying models in app/models.py:

bash
uv run alembic revision --autogenerate -m "Add new column"

Review the generated migration in alembic/versions/, then apply:

bash
uv run alembic upgrade head

Seed Data

Seed Problems

Load initial problems from YAML files:

bash
uv run python scripts/seed_problems.py seed

This loads problems from backend/data/problems/.

Verify Seeded Data

Via API:

bash
curl http://localhost:8000/api/problems

Via Supabase Studio:

  1. Open Supabase Studio
  2. Go to Table Editor > problems
  3. You should see 15+ problems

Via psql:

bash
psql $DATABASE_URL
SELECT title, difficulty FROM problems;
\q

Database Schema

problems

sql
CREATE TABLE problems (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  title TEXT NOT NULL,
  slug TEXT UNIQUE NOT NULL,
  description TEXT NOT NULL,
  difficulty TEXT CHECK (difficulty IN ('easy', 'medium', 'hard')),
  pattern TEXT NOT NULL,
  sequence_number INTEGER UNIQUE NOT NULL,
  constraints JSONB NOT NULL,
  examples JSONB NOT NULL,
  created_at TIMESTAMP DEFAULT NOW()
);

problem_languages

sql
CREATE TABLE problem_languages (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  problem_id UUID REFERENCES problems(id) ON DELETE CASCADE,
  language TEXT NOT NULL,
  starter_code TEXT NOT NULL,
  reference_solution TEXT NOT NULL,
  function_signature JSONB NOT NULL,
  UNIQUE(problem_id, language)
);

test_cases

sql
CREATE TABLE test_cases (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  problem_id UUID REFERENCES problems(id) ON DELETE CASCADE,
  input JSONB NOT NULL,
  expected JSONB NOT NULL,
  sequence INTEGER NOT NULL,
  UNIQUE(problem_id, sequence)
);

user_progress

sql
CREATE TABLE user_progress (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id VARCHAR(255) NOT NULL,
  problem_id UUID REFERENCES problems(id) ON DELETE CASCADE,
  times_solved INTEGER DEFAULT 0,
  last_solved_at TIMESTAMP,
  next_review_date DATE,
  is_mastered BOOLEAN DEFAULT FALSE,
  show_again BOOLEAN DEFAULT FALSE,
  created_at TIMESTAMP DEFAULT NOW(),
  UNIQUE(user_id, problem_id)
);

submissions

sql
CREATE TABLE submissions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id VARCHAR(255) NOT NULL,
  problem_id UUID REFERENCES problems(id) ON DELETE CASCADE,
  code TEXT NOT NULL,
  language TEXT NOT NULL,
  passed BOOLEAN NOT NULL,
  runtime_ms INTEGER,
  submitted_at TIMESTAMP DEFAULT NOW()
);

Backup and Restore

Backup (Supabase Cloud)

Use pg_dump via Supabase connection:

bash
pg_dump "postgresql://postgres:PASSWORD@db.PROJECT_ID.supabase.co:5432/postgres" > backup.sql

Restore

bash
psql "postgresql://postgres:PASSWORD@db.PROJECT_ID.supabase.co:5432/postgres" < backup.sql

Backup (Local Supabase)

bash
supabase db dump -f backup.sql

Troubleshooting

"Connection refused"

  • Verify database is running (Supabase dashboard or pg_isready)
  • Check DATABASE_URL format: postgresql+asyncpg:// (not postgresql://)
  • Ensure your IP is allowlisted in Supabase

"SSL required"

Add ?sslmode=require to connection string:

postgresql+asyncpg://...?sslmode=require

Migration errors

bash
# Check current migration state
uv run alembic current

# View pending migrations
uv run alembic history

# Force to specific version (use with caution)
uv run alembic stamp head

"Database does not exist"

  • Verify database name in connection string
  • Create database if using plain PostgreSQL
  • Check Supabase project status

Slow queries

Add indexes for frequently queried columns:

sql
CREATE INDEX idx_user_progress_user_id ON user_progress(user_id);
CREATE INDEX idx_user_progress_next_review ON user_progress(next_review_date);

(These are already in migrations)

Next Steps