Skip to main content

Entry point

POST /api/v1/search or GET /api/v1/searchSearchControllerSearchService::findTutors() File: app/Models/Services/v1/SearchService.php

Search parameters

The Search model (app/Models/v1/Search.php) defines the search criteria:
ParameterTypeDescription
pathstringSubject slug/path
datedateRequested lesson date
dayOfTheWeekstringDay of week for availability
durationintLesson duration in minutes
availabilityTimesarrayRequested time slots
subjectIdintSubject filter
subjectLevelIdsarraySubject level filters
examBoardsarrayExam board filters
lat / lngfloatSearch location coordinates
sortstringSort order
filtersarrayAdditional filters (see below)

Filter keys

KeyValues
FILTER_LESSON_TYPEonline, tutors_home, students_home
FILTER_GENDERmale, female
FILTER_LANGUAGElanguage ID

How search works

1

Resolve coordinates

If no lat/lng provided, PostcodeService looks up coordinates from the postcode. Results are cached for 3 months via postcodes.io API.
2

Build base query

Starts with Tutor::query() (already scoped to role_id=1) and chains multiple scopes:
Tutor::withBasicInfo()
    ->isVisible()
    ->whoTeaches($subjectId)
    ->whoHasValidAddress()
    ->whoHasValidMobileNumber()
    ->whoHasValidQualificationOrEducation()
    ->whoHasValidDBS()
    // ... more scopes
3

Filter by availability

Uses isAvailableOnDay() or isAvailableOnDate() scopes. Checks:
  • Tutor has availability on the requested day/date
  • Tutor is not on holiday (unavailable_from/unavailable_until)
  • No clashing existing lessons
  • Available time slots match requested times
4

Filter by location

For in-person lessons:
  • withDistance($lat, $lng) — adds a calculated distance column using the Haversine formula
  • withinDistance() — filters by tutor’s max_travel_distance
For online lessons: location filtering is skipped.
5

Apply additional filters

Gender, language, lesson type filters applied via withFilters() scope.
6

Check advance booking window

getAllUnavailableTutorIds() checks a 14-day advance booking window. Tutors unavailable across the entire window are excluded.
const MAX_ADVANCE_BOOKING_AVAILABILITY = 14; // days
7

Sort and paginate

Results sorted via sortResultsBy() scope and returned with pagination via ServicePaginatorResponse.

Response format

Transformed via SearchTransformer:
{
  "id": 123,
  "firstname": "Jane",
  "lastname": "D",
  "profile_picture_url": "...",
  "location": {
    "distance": 5  // km, ceiled
  },
  "profile": {
    "tutoring_experience": "2 years 3 months"
  },
  "teaching_info": {
    "teach_id": 45,
    "subject": { "id": 1, "title": "Mathematics" },
    "subject_level": { "id": 2, "title": "GCSE" },
    "exam_boards": [...]
  },
  "lesson_price_breakdown": {
    "sign": "£",
    "currency_code": "GBP",
    "lesson_price": "30.00",
    "service_fee": "3.00",
    "total": "33.00"
  }
}

BlackBox distance service

For travel time calculations between tutor and student locations, the API calls the BlackBox microservice (runs as a Docker sidecar on port 3000). File: app/BlackBox/Models/Distance.php
  • Calculates transit distance/duration between two UK postcodes
  • Results cached for 2 days
  • Returns DistanceResponse with getDuration() (seconds) and getMilesAway() (converted from meters)
  • Uses Google Maps Distance Matrix API under the hood

PostcodeService

File: app/Models/Services/PostcodeService/PostcodeService.php
  • Fetches postcode coordinates from postcodes.io public API
  • Results cached for 3 months
  • Returns PostcodeResponse with getLat() and getLng()