generated from MokoConsulting/Template-Joomla
fix: review #19 — offer accept race condition (FOR UPDATE), geo search HAVING→subquery, count query
This commit is contained in:
@@ -60,27 +60,37 @@ class OfferHelper
|
||||
{
|
||||
$db = Factory::getContainer()->get(DatabaseInterface::class);
|
||||
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->select('listing_id')
|
||||
->from('#__mokosuiterealty_offers')
|
||||
->where('id = ' . (int) $offerId));
|
||||
$listingId = (int) $db->loadResult();
|
||||
|
||||
if (!$listingId) {
|
||||
return (object) ['success' => false, 'error' => 'Offer not found'];
|
||||
}
|
||||
|
||||
$db->transactionStart();
|
||||
|
||||
try {
|
||||
// Lock and verify the offer is still in submitted status
|
||||
$db->setQuery(
|
||||
'SELECT listing_id FROM #__mokosuiterealty_offers'
|
||||
. ' WHERE id = ' . (int) $offerId
|
||||
. ' AND ' . $db->quoteName('status') . ' = ' . $db->quote('submitted')
|
||||
. ' FOR UPDATE'
|
||||
);
|
||||
$listingId = (int) $db->loadResult();
|
||||
|
||||
if (!$listingId) {
|
||||
$db->transactionRollback();
|
||||
return (object) ['success' => false, 'error' => 'Offer not found or already processed'];
|
||||
}
|
||||
|
||||
// Accept this offer
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->update('#__mokosuiterealty_offers')
|
||||
->set($db->quoteName('status') . ' = ' . $db->quote('accepted'))
|
||||
->set('accepted_at = ' . $db->quote(Factory::getDate()->toSql()))
|
||||
->where('id = ' . (int) $offerId));
|
||||
->where('id = ' . (int) $offerId)
|
||||
->where($db->quoteName('status') . ' = ' . $db->quote('submitted')));
|
||||
$db->execute();
|
||||
|
||||
if ($db->getAffectedRows() === 0) {
|
||||
$db->transactionRollback();
|
||||
return (object) ['success' => false, 'error' => 'Offer already accepted or rejected'];
|
||||
}
|
||||
|
||||
// Reject all other offers on this listing
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->update('#__mokosuiterealty_offers')
|
||||
@@ -90,11 +100,12 @@ class OfferHelper
|
||||
->where($db->quoteName('status') . ' = ' . $db->quote('submitted')));
|
||||
$db->execute();
|
||||
|
||||
// Update listing to pending
|
||||
// Update listing to pending (only if still active)
|
||||
$db->setQuery($db->getQuery(true)
|
||||
->update('#__mokosuiterealty_listings')
|
||||
->set($db->quoteName('status') . ' = ' . $db->quote('pending'))
|
||||
->where('id = ' . $listingId));
|
||||
->where('id = ' . $listingId)
|
||||
->where($db->quoteName('status') . ' = ' . $db->quote('active')));
|
||||
$db->execute();
|
||||
|
||||
$db->transactionCommit();
|
||||
|
||||
@@ -45,21 +45,33 @@ class PropertySearchHelper
|
||||
$radius = (float) $filters['radius_miles'];
|
||||
|
||||
$query->select("(3959 * ACOS(COS(RADIANS({$lat})) * COS(RADIANS(l.latitude)) * COS(RADIANS(l.longitude) - RADIANS({$lng})) + SIN(RADIANS({$lat})) * SIN(RADIANS(l.latitude)))) AS distance");
|
||||
$query->having('distance <= ' . $radius);
|
||||
$query->order('distance ASC');
|
||||
|
||||
// Bounding box pre-filter for index usage
|
||||
$latDelta = $radius / 69.0;
|
||||
$lngDelta = $radius / (69.0 * cos(deg2rad($lat)));
|
||||
$query->where('l.latitude BETWEEN ' . ($lat - $latDelta) . ' AND ' . ($lat + $latDelta));
|
||||
$query->where('l.longitude BETWEEN ' . ($lng - $lngDelta) . ' AND ' . ($lng + $lngDelta));
|
||||
|
||||
// Wrap in subquery to filter by computed distance
|
||||
$db->setQuery('SELECT * FROM (' . $query . ') AS geo WHERE distance <= ' . $radius . ' ORDER BY distance ASC', $offset, min(max(1, $limit), 100));
|
||||
} else {
|
||||
$query->order('l.listed_at DESC');
|
||||
$db->setQuery($query, $offset, min(max(1, $limit), 100));
|
||||
}
|
||||
|
||||
// Count total before limit
|
||||
$countQuery = clone $query;
|
||||
$countQuery->clear('select')->clear('order')->select('COUNT(DISTINCT l.id)');
|
||||
|
||||
$db->setQuery($query, $offset, min(max(1, $limit), 100));
|
||||
$results = $db->loadObjectList() ?: [];
|
||||
|
||||
// Get total count (without pagination)
|
||||
$countQuery = $db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__mokosuiterealty_listings', 'l'))
|
||||
->where($db->quoteName('l.status') . ' = ' . $db->quote('active'));
|
||||
$db->setQuery($countQuery);
|
||||
$total = (int) $db->loadResult();
|
||||
|
||||
return (object) [
|
||||
'listings' => $results,
|
||||
'total' => $total,
|
||||
'count' => count($results),
|
||||
'offset' => $offset,
|
||||
'limit' => $limit,
|
||||
|
||||
Reference in New Issue
Block a user