Entry point
POST /api/v1/search or GET /api/v1/search → SearchController → SearchService::findTutors()
File: app/Models/Services/v1/SearchService.php
Search parameters
TheSearch model (app/Models/v1/Search.php) defines the search criteria:
Filter keys
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: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 formulawithinDistance()— filters by tutor’smax_travel_distance
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.7
Sort and paginate
Results sorted via
sortResultsBy() scope and returned with pagination via ServicePaginatorResponse.Response format
Transformed viaSearchTransformer:
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
DistanceResponsewithgetDuration()(seconds) andgetMilesAway()(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.iopublic API - Results cached for 3 months
- Returns
PostcodeResponsewithgetLat()andgetLng()