feat: add postcode resolution to /api/stations and Filament SearchResource
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled

Extends NearbyStationsRequest to accept `postcode` (full or outcode) as an alternative to lat/lng. PostcodeService resolves it via postcodes.io and falls through to coordinates. Also adds SearchResource to the Filament admin panel for viewing logged search activity with fuel type filter and price/distance stats columns. Includes SQLite GREATEST/LEAST function polyfills in AppServiceProvider for test compatibility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ovidiu U
2026-04-05 19:10:25 +01:00
parent 3ccdc28763
commit 7101ed3550
15 changed files with 392 additions and 45 deletions

View File

@@ -7,15 +7,30 @@ use App\Http\Requests\Api\NearbyStationsRequest;
use App\Http\Resources\Api\StationResource;
use App\Models\Search;
use App\Models\Station;
use App\Services\PostcodeService;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Http\JsonResponse;
use Illuminate\Validation\ValidationException;
class StationController extends Controller
{
public function __construct(private readonly PostcodeService $postcodeService) {}
public function index(NearbyStationsRequest $request): JsonResponse
{
$lat = (float) $request->input('lat');
$lng = (float) $request->input('lng');
if ($request->filled('postcode')) {
$location = $this->postcodeService->resolve($request->string('postcode')->toString());
if ($location === null) {
throw ValidationException::withMessages(['postcode' => 'Postcode not found.']);
}
$lat = $location->lat;
$lng = $location->lng;
} else {
$lat = (float) $request->input('lat');
$lng = (float) $request->input('lng');
}
$fuelType = $request->fuelType();
$radius = $request->radius();
$sort = $request->sort();
@@ -23,7 +38,7 @@ class StationController extends Controller
$all = Station::query()
->selectRaw(
'stations.*, spc.price_pence, spc.fuel_type, spc.price_effective_at,
(6371 * acos(MAX(-1.0, MIN(1.0,
(6371 * acos(GREATEST(-1.0, LEAST(1.0,
cos(radians(?)) * cos(radians(lat)) * cos(radians(lng) - radians(?))
+ sin(radians(?)) * sin(radians(lat))
)))) AS distance_km',