Skip to main content

Data Models (ERD)

Overview

This document describes the entity-relationship diagram (ERD) and data models for the Learnille platform.

Database Schema

Core Entities

User Management

-- Users table
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
first_name VARCHAR(100),
last_name VARCHAR(100),
avatar_url VARCHAR(500),
bio TEXT,
role VARCHAR(50) NOT NULL DEFAULT 'student', -- student, instructor, admin
is_active BOOLEAN DEFAULT true,
email_verified BOOLEAN DEFAULT false,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- User profiles table (extended information)
CREATE TABLE user_profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
phone VARCHAR(20),
location VARCHAR(255),
website VARCHAR(255),
social_links JSONB,
preferences JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

Course Management

-- Categories table
CREATE TABLE categories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL,
slug VARCHAR(100) UNIQUE NOT NULL,
description TEXT,
parent_id UUID REFERENCES categories(id),
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Courses table
CREATE TABLE courses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title VARCHAR(255) NOT NULL,
slug VARCHAR(255) UNIQUE NOT NULL,
description TEXT,
short_description VARCHAR(500),
thumbnail_url VARCHAR(500),
instructor_id UUID REFERENCES users(id) ON DELETE CASCADE,
category_id UUID REFERENCES categories(id),
level VARCHAR(50) DEFAULT 'beginner', -- beginner, intermediate, advanced
language VARCHAR(10) DEFAULT 'en',
price DECIMAL(10,2) DEFAULT 0,
currency VARCHAR(3) DEFAULT 'USD',
is_published BOOLEAN DEFAULT false,
is_featured BOOLEAN DEFAULT false,
enrollment_count INTEGER DEFAULT 0,
rating DECIMAL(3,2) DEFAULT 0,
review_count INTEGER DEFAULT 0,
duration_hours INTEGER,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Course sections
CREATE TABLE course_sections (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
course_id UUID REFERENCES courses(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
description TEXT,
order_index INTEGER NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Course lessons
CREATE TABLE course_lessons (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
section_id UUID REFERENCES course_sections(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
description TEXT,
content_type VARCHAR(50) NOT NULL, -- video, text, quiz, assignment
content_url VARCHAR(500),
content_text TEXT,
duration_minutes INTEGER,
order_index INTEGER NOT NULL,
is_preview BOOLEAN DEFAULT false,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

Enrollment and Progress

-- Enrollments table
CREATE TABLE enrollments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
course_id UUID REFERENCES courses(id) ON DELETE CASCADE,
enrollment_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
completion_date TIMESTAMP WITH TIME ZONE,
progress_percentage DECIMAL(5,2) DEFAULT 0,
is_completed BOOLEAN DEFAULT false,
certificate_issued BOOLEAN DEFAULT false,
UNIQUE(user_id, course_id)
);

-- Lesson progress
CREATE TABLE lesson_progress (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
enrollment_id UUID REFERENCES enrollments(id) ON DELETE CASCADE,
lesson_id UUID REFERENCES course_lessons(id) ON DELETE CASCADE,
is_completed BOOLEAN DEFAULT false,
completed_at TIMESTAMP WITH TIME ZONE,
time_spent_minutes INTEGER DEFAULT 0,
last_accessed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(enrollment_id, lesson_id)
);

Consultation System

-- Consultation types
CREATE TABLE consultation_types (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL,
description TEXT,
is_active BOOLEAN DEFAULT true
);

-- Consultations table
CREATE TABLE consultations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title VARCHAR(255) NOT NULL,
description TEXT,
instructor_id UUID REFERENCES users(id) ON DELETE CASCADE,
consultation_type_id UUID REFERENCES consultation_types(id),
category_id UUID REFERENCES categories(id),
thumbnail_url VARCHAR(500),
price DECIMAL(10,2) DEFAULT 0,
currency VARCHAR(3) DEFAULT 'USD',
duration_minutes INTEGER,
max_participants INTEGER DEFAULT 1,
is_published BOOLEAN DEFAULT false,
is_available BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Consultation bookings
CREATE TABLE consultation_bookings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
consultation_id UUID REFERENCES consultations(id) ON DELETE CASCADE,
student_id UUID REFERENCES users(id) ON DELETE CASCADE,
scheduled_at TIMESTAMP WITH TIME ZONE NOT NULL,
status VARCHAR(50) DEFAULT 'pending', -- pending, confirmed, completed, cancelled
meeting_url VARCHAR(500),
notes TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

Payment System

-- Payment methods
CREATE TABLE payment_methods (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
type VARCHAR(50) NOT NULL, -- card, paypal, bank_transfer
provider VARCHAR(50) NOT NULL, -- stripe, paypal
provider_payment_method_id VARCHAR(255),
last_four VARCHAR(4),
expiry_month INTEGER,
expiry_year INTEGER,
is_default BOOLEAN DEFAULT false,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Payments table
CREATE TABLE payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
payment_method_id UUID REFERENCES payment_methods(id),
amount DECIMAL(10,2) NOT NULL,
currency VARCHAR(3) DEFAULT 'USD',
status VARCHAR(50) DEFAULT 'pending', -- pending, processing, completed, failed, refunded
provider VARCHAR(50) NOT NULL,
provider_payment_id VARCHAR(255),
description VARCHAR(255),
metadata JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Invoices table
CREATE TABLE invoices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
payment_id UUID REFERENCES payments(id),
invoice_number VARCHAR(100) UNIQUE NOT NULL,
amount DECIMAL(10,2) NOT NULL,
currency VARCHAR(3) DEFAULT 'USD',
status VARCHAR(50) DEFAULT 'unpaid', -- unpaid, paid, overdue, cancelled
due_date DATE,
paid_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

Reviews and Ratings

-- Reviews table
CREATE TABLE reviews (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
course_id UUID REFERENCES courses(id) ON DELETE CASCADE,
rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 5),
title VARCHAR(255),
comment TEXT,
is_verified BOOLEAN DEFAULT false, -- verified purchase
helpful_count INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(user_id, course_id)
);

-- Review responses (instructor replies)
CREATE TABLE review_responses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
review_id UUID REFERENCES reviews(id) ON DELETE CASCADE,
instructor_id UUID REFERENCES users(id) ON DELETE CASCADE,
response TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

Entity Relationship Diagram

┌─────────────────┐ ┌─────────────────┐
│ users │ │ user_profiles │
│─────────────────│ │─────────────────│
│ id (PK) │1────1│ id (PK) │
│ email │ │ user_id (FK) │
│ password_hash │ │ phone │
│ first_name │ │ location │
│ last_name │ │ website │
│ role │ │ social_links │
│ is_active │ │ preferences │
└─────────────────┘ └─────────────────┘


┌────┴────┐
│ │
┌───▼───┐ ┌───▼───┐
│courses│ │reviews │
│───────│ │───────│
│id (PK)│ │id (PK) │
│title │ │user_id│
│instructor││course_id│
│category│ │rating │
│price │ │comment│
└───┬───┘ └───────┘


┌───▼────┐
│enrollments│
│──────────│
│id (PK) │
│user_id │
│course_id │
│progress │
│completed │
└───┬─────┘


┌───▼──────┐
│lesson_progress│
│──────────────│
│id (PK) │
│enrollment_id │
│lesson_id │
│completed │
│time_spent │
└──────────────┘

Key Relationships

One-to-One Relationships

  • User → User Profile
  • Payment → Invoice

One-to-Many Relationships

  • User → Courses (as instructor)
  • User → Enrollments
  • User → Reviews
  • User → Payments
  • User → Payment Methods
  • Course → Course Sections
  • Course Section → Course Lessons
  • Course → Reviews
  • Enrollment → Lesson Progress
  • Consultation → Consultation Bookings

Many-to-Many Relationships

  • Users ↔ Courses (through Enrollments)
  • Users ↔ Consultations (through Bookings)

Indexes

-- Performance indexes
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_role ON users(role);
CREATE INDEX idx_courses_instructor ON courses(instructor_id);
CREATE INDEX idx_courses_category ON courses(category_id);
CREATE INDEX idx_courses_published ON courses(is_published);
CREATE INDEX idx_enrollments_user ON enrollments(user_id);
CREATE INDEX idx_enrollments_course ON enrollments(course_id);
CREATE INDEX idx_reviews_course ON reviews(course_id);
CREATE INDEX idx_reviews_rating ON reviews(rating);
CREATE INDEX idx_payments_user ON payments(user_id);
CREATE INDEX idx_payments_status ON payments(status);

Data Constraints

  • Email uniqueness across users
  • Course slug uniqueness
  • Enrollment uniqueness per user-course pair
  • Rating values between 1-5
  • Positive price values
  • Future dates for scheduled consultations
  • Non-negative progress percentages

Data Migration Strategy

  1. Initial Schema: Create all tables with basic structure
  2. Seed Data: Populate reference data (categories, consultation types)
  3. Data Migration: Migrate existing data if applicable
  4. Index Creation: Add performance indexes after data loading
  5. Constraint Addition: Add foreign key constraints after data validation

Backup and Recovery

  • Daily automated backups of PostgreSQL database
  • Point-in-time recovery capability
  • Cross-region backup replication
  • Regular backup testing and validation