Properties Module
Overview
The Properties module powers the house search, map, likes, enquiries, and city features for both the mobile app and web app. It is the largest module by code surface and makes extensive use of Spatie Query Builder for filterable, sortable, paginated endpoints.
Routes
Routes are prefixed with /properties (configured in RouteServiceProvider).
# Legacy routes (web app / search)
GET /properties/ List properties (legacy, uses PropertyService)
GET /properties/cities List cities
GET /properties/{id} Show property
GET /properties/agent-bookable/{house} Check if agent has bookable viewings
GET /properties/bills-price Get bills price for bedrooms/city
POST /properties/enquiry Submit property enquiry
GET /properties/similar/{house} Get similar properties
GET /properties/map-highlights/{house} Get nearby map highlights
# Authenticated legacy
GET /properties/cities/demo Demo cities (requires JWT)
# v1 Explore routes (mobile app, uses Spatie Query Builder)
GET /properties/v1/houses/search Search terms/autocomplete
GET /properties/v1/houses List houses (filterable, sortable, paginated)
GET /properties/v1/houses/{house} Show house detail
GET /properties/v1/map-markers Map marker data
# Authenticated v1 routes
PATCH /properties/v1/user/city Update user's city
POST /properties/v1/houses/{house}/like Toggle house likeThe /properties/v1/ routes use the OptionalJWTAuth middleware, which resolves the user if a token is present but allows anonymous access. Authenticated users get extra data (likes, viewing requests, group likes).
Controllers
HouseController (Spatie Query Builder)
The primary search controller for the mobile app explore screen.
index(Request) — Returns paginated, filtered, sorted houses:
Allowed Filters:
| Filter | Type | Description |
|---|---|---|
city | Exact | Filter by city name |
boundary | Custom (BoundaryFilter) | Geographic boundary (bounding box or radius) |
is_liked | Custom | Only liked houses (requires auth) |
is_group_liked | Custom | Only group-liked houses (requires auth) |
price_min | Custom | Minimum total price (rent + bills) |
price_max | Custom | Maximum total price (rent + bills) |
bedrooms | Exact | Number of bedrooms |
bathrooms | Exact | Number of bathrooms |
crm_block_id | Custom | CRM block ID (US only) |
property_operator | Exact | Property operator ID |
agent_id | Exact | Agent ID |
uob_approved | Custom | University-approved agents only |
academic_year | Custom | Academic year (US lease terms) |
Allowed Sorts:
| Sort | Class | Description |
|---|---|---|
popular | PopularitySort | By tap count |
recommended | RecommendedSort | By tier, priority, random (default) |
newest | NewestSort | By creation date |
highest_price | HighestPriceSort | Total price descending |
lowest_price | LowestPriceSort | Total price ascending |
most_liked | MostLikedSort | By like count |
Allowed Includes:
viewingRequest— User’s viewing request for each housevideoTour— Video tour link
Example request:
GET /properties/v1/houses?filter[city]=London&filter[bedrooms]=3&filter[price_min]=100&filter[boundary]=51.52,51.50,-0.15,-0.10&sort=lowest_price&page=2show(House) — Returns a single house with all relations loaded.
PropertiesController (Legacy)
Serves the web app search. Uses PropertyService directly (not Spatie Query Builder).
index(PropertyRequest)— Legacy search with query params for city, price range, bedrooms, bathrooms, coordinates, sort, paginationshow(int $id)— Legacy single property viewsimilar(House)— Similar properties by price range and bedroomsnearbyHighlights(NearbyHighlightRequest)— Map highlights near a propertygetBillsPrice(BillsPriceRequest)— Bills price calculationgetAgentBookable(House)— Check if agent supports online booking
MapMarkerController
index()— Returns lightweight map marker data for all visible houses
HouseLikeController
store(House)— Toggle like/unlike for authenticated user
CitiesController
index()— List all active citiesdemoCities()— Demo cities (authenticated)update()— Update user’s selected city
HouseSearchTermsController
__invoke()— Returns search terms/autocomplete data
EnquiryController
enquiry(EnquiryRequest)— Submit a property enquiry. CreatesWebsiteEnquiryrecord, sends confirmation email to user, notification email to landlord.
Models
House
The core model. Lives in the main database (houses table), shared with v1.
Key features:
scopeWithBillsPrice()— Cross-database join to calculatebills_priceandtotal_pricefrom the bills database pricing tablesscopeWithinCoordinates()— Spatial query usingmapped_housestable (UK only, MySQL 8)scopeWithinLatLngRadius()— Radius-based spatial query (UK only)- Soft deletes enabled
- Uses
HousePolicyfor authorisation - Relations:
agent,likedHouses,viewingRequest,videoTour,buildingDetail(US),leaseTerms(US),propertyOperator,billsIncludedAgent
MappedHouse
Spatial data for houses. Contains a POINT geometry column for efficient spatial queries. Populated via database triggers when houses are created/updated. UK only (MySQL 8 spatial features).
LikedHouse
User house likes. Fields: house_id, tenant_id, status, match_preference.
HouseLeaseTerm
US-only. Lease terms per academic year with rent, deposit, start/end dates, and apply now links.
City
City data with coordinates.
SearchArea
Search area boundaries for city-based filtering.
WebsiteEnquiry
Property enquiry records.
BookableViewing
Agent bookable viewing slot data.
BillsPrice / BillsPricesSelectable / BillsIncludedAgent
Bills pricing configuration. These models bridge the main DB and bills DB for the withBillsPrice scope.
BuildingDetail
US-only. Building-level details (title, logo, cover image) linked via crm_block_id.
VideoTour
Video tour links for houses.
HousemateGroup
Housemate groups for group likes and group enquiry features.
ViewingRequest
Viewing/enquiry requests from the Rental module (cross-module relation).
Services
PropertyService
Legacy search service with manual query building. Supports filtering by city, price range, bedrooms, bathrooms, coordinates, and sorting. Also handles CRUD operations for properties (used by external API clients).
BillsPriceService
Calculates bills price for a given bedrooms/city/agent combination. Accounts for bills-included agents.
MapService
Handles map highlight data (nearby points of interest).
EnquiryService
Processes property enquiries, sends emails.
Custom Filters (Spatie)
| Filter | Description |
|---|---|
BoundaryFilter | Geographic filtering. Accepts bounding box (4 values: N,S,W,E) or radius (3 values: lat,lng,km) |
IsLikedFilter | Filter to user’s liked houses |
IsGroupLikedFilter | Filter to houses liked by group members |
PriceMinFilter | Minimum total price |
PriceMaxFilter | Maximum total price |
CrmBlockIdFilter | CRM block ID filter (US) |
UobApprovedFilter | University-approved agents |
AcademicYearFilter | Academic year for lease terms (US) |
Custom Sorts (Spatie)
| Sort | Logic |
|---|---|
RecommendedSort | tier ASC, house_priority DESC, RAND() |
PopularitySort | By tap count |
NewestSort | By created_at |
HighestPriceSort | By total_price DESC |
LowestPriceSort | By total_price ASC |
MostLikedSort | By like count |
Queries
HouseAvailabilityScopes
Applies global availability constraints to house queries (status filtering, CRM group deduplication).
MapMarkersQuery
Optimised query for map marker data (lightweight projection of house locations).
Actions
FormatHouseAddressAction
Static action that formats a house address for display. Has a dedicated unit test.
Resource
HouseResource
Transforms House model to JSON. Key fields:
- Price fields:
price_pw,price_pm,deposit,bills_price,total_price - Location:
city,address,lat,lng - Media:
images(resized via ServerlessImageHandler),first_image,floorplan,video_tour - Social:
is_liked,liked_at,group_likes,group_enquiries,is_popular - US-specific:
squareFootage,crm_name,crm_block_id,building_title,building_logo_image - Conditional:
viewing_request_status,viewing_request_created_at(only when loaded)
Images from the Housr S3 bucket are automatically resized to 800x640 via the Serverless Image Handler (sabatino/serverless-image-handler package).
Traits
HasGroupMemberLikes— Adds group member like relations and helpersHasGroupMemberEnquired— Adds group member enquiry relations and helpers
Policies
HousePolicy— Authorisation rules for house operations
Tests
Modules/Properties/tests/Unit/Actions/FormatHouseAddressActionTest.php— Unit test for address formatting