Skip to main content

Configuration

  • Engine: MySQL 5.7
  • Charset: utf8mb4 (supports emoji/unicode)
  • Collation: utf8mb4_unicode_ci
  • Strict mode: Enabled
  • Connections: mysql (primary), mysql_test (test database)
Config: config/database.php

Core tables

Users & roles

users
├── id, firstname, lastname, email, password, username
├── role_id → roles (TUTOR=1, STUDENT=2, PARENT=3, INTERNAL=200)
├── country_id → countries
├── gender (0=UNDEFINED, 1=MALE, 2=FEMALE)
├── is_visible (boolean — tutor searchability)
├── profile_picture_url, video_url
├── unavailable_from, unavailable_until (holiday dates)
├── email_verified_at
└── deleted_at (soft deletes)
The Tutor model applies a global scope filtering to role_id = 1.

Lessons & bookings

lessons
├── id, start, finish, duration (auto-calculated in minutes)
├── suggested_start, suggested_finish (for reschedule proposals)
├── state (0=CANCELLED, 1=ACTIVE, 2=RESCHEDULE)
├── tutor_id → users
├── subject_id → subjects
├── level_id → subject_levels
├── exam_board_id → exam_boards (nullable)
├── teach_id → teaches
├── experience_rating (1-5, post-lesson)
├── cancellation_note
└── deleted_at (soft deletes)

bookings
├── id, state
│   (0=REFUNDED, 1=PAYED, 2=PENDING_APPROVAL,
│    3=AWAITING_PAYMENT, 4=PARTIAL_REFUNDED, 5=REQUIRES_CAPTURE)
├── lesson_id → lessons
├── user_id → users (the student/parent who booked)
├── availability_id → availabilities (nullable)
└── deleted_at (soft deletes)

Payments

payment_accounts
├── id, account_id (Stripe customer/connect ID)
├── user_id → users
└── payment_gateway_id → payment_gateways

payment_receipts
├── id, charge_id (Stripe charge ID)
├── amount, application_fee_id, application_fee_amount
├── transfer_id (Stripe transfer ID for payout)
├── booking_id → bookings
├── commission_id → commissions
├── coupon_id → coupons (nullable)
├── card_id → cards
└── payment_account_id → payment_accounts

cards (soft deletes)
├── id, card_id (Stripe payment method ID), token
├── brand, last_4, exp_month, exp_year
├── is_default (boolean)
└── payment_account_id → payment_accounts

bank_accounts (soft deletes)
├── id, account_id, sort_code, account_number
└── payment_account_id → payment_accounts

commissions
├── id, commission_fee, service_fee
├── payout_fee, payout_amount_fee
├── account_management_amount_fee
└── is_enabled (boolean — only one active at a time)

subscriptions (soft deletes)
├── id, stripe_id, stripe_status, stripe_plan
├── trial_end (nullable)
└── user_id → users

Teaching & availability

teaches (soft deletes)
├── id, user_id → users (tutor)
├── subject_id → subjects
├── is_enabled (boolean)
└── Each has one lesson_prices entry

lesson_prices
├── id, hourly_rate
└── teach_id → teaches

day_availabilities
├── id, day (day of week mapped to generic dates)
└── user_id → users (tutor)

availabilities
├── id, from, to (datetime)
├── preferred_duration
├── user_id → users
└── day_availability_id → day_availabilities

Verification

verification_accounts (soft deletes)
├── id, status (UPLOAD_DOCUMENT, PASS, FAIL, PENDING, INTERNAL_REVIEW)
├── applicant_id (external Onfido/Yoti ID)
├── user_id → users
├── file_id → files
└── verification_gateway_id → verification_gateways

verification_dbs
├── id, status (UPLOAD_DOCUMENT, PENDING, PASS, FAIL)
├── certificate_number, countersignatory, registered_by
├── date_of_issue, document_image_url
└── user_id → users

verification_qualifications
├── id, status (UPLOAD_DOCUMENT, PENDING, PASS, FAIL)
├── document_image_url
└── user_id → users

User details

personal_details
├── id, gender, date_of_birth, name_title_id → name_titles
└── user_id → users

addresses
├── id, line_1, line_2, line_3, line_4, post_code
├── country_name, country_id → countries
├── is_primary (boolean)
└── personal_detail_id → personal_details

profiles
├── id, bio (500 char max)
├── teacher_reference_number (string, optional)
├── teaching_experience
├── tutoring_experience (stored in MONTHS)
└── user_id → users

locations
├── id, post_code, region, lat, lng
├── max_travel_distance
└── user_id → users

Pivot tables

TableRelationship
lesson_userLessons ↔ Students (many-to-many)
lesson_childLessons ↔ Children (many-to-many)
coupon_usesUsers ↔ Coupons (usage tracking)
level_subjectSubjectLevels ↔ Subjects
taught_subject_levelsTeaches ↔ SubjectLevels
taught_exam_boardsTeaches ↔ ExamBoards
language_userUsers ↔ Languages
fileablesPolymorphic file attachments

Migrations

~129 migration files in database/migrations/.
# Run all migrations
php artisan migrate --force

# Run on test database
php artisan migrate --database=mysql_test --force

# Rollback
php artisan migrate:rollback

Important gotchas

Tutor addresses: Adding a new address deletes the existing one. Tutors are limited to 1 address. Students can have multiple.
Commission model: Only one commission can be active at a time. Commission::addNewModel() disables all existing commissions before inserting the new one.
PaymentReceipt boot method: Auto-calculates the commission breakdown on every model retrieval. This hits CommissionService on every PaymentReceipt::find(). Watch for N+1 performance issues on bulk queries.
profiles.tutoring_experience is stored in months, not years. The Profile::getTutoringExperience() method formats it as “X years Y months” for display.
files table uses non-incrementing UUID primary keys (string), not auto-increment integers.