Remove prediction API endpoint and integrate into stations search
Consolidate prediction functionality by merging /api/prediction endpoint into /api/stations response. Move prediction logic from PredictionController into StationController, returning prediction data alongside station results. Replace usePrediction composable with unified useStations that returns {stations, meta, prediction}. Remove PredictionRequest, related tests, and unused Vue components (FuelFinderTest, MapTest, RecommendationTest, StationListTest). Add PredictionFull component and UpsellBanner. Extend NationalFuelPredictionService to include weekly_summary (7-day series, yesterday/today averages, cheapest/priciest days) and oil signal from price_predictions table. Update Home.vue to consume prediction from stations response. Add Plan::resolveCadenceForUser helper and configure Cashier to use custom Subscription model.
This commit is contained in:
@@ -63,10 +63,19 @@ class AuthController extends Controller
|
||||
public function me(Request $request): JsonResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
$subscription = $user->subscription();
|
||||
|
||||
$expiresAt = $subscription?->ends_at ?? $subscription?->current_period_end;
|
||||
|
||||
return response()->json(array_merge(
|
||||
$user->toArray(),
|
||||
['tier' => Plan::resolveForUser($user)->name],
|
||||
[
|
||||
'tier' => Plan::resolveForUser($user)->name,
|
||||
'subscription_cancelled' => $subscription?->canceled() ?? false,
|
||||
'subscription_cadence' => Plan::resolveCadenceForUser($user),
|
||||
'subscribed_at' => $subscription?->created_at?->toIso8601String(),
|
||||
'subscription_expires_at' => $expiresAt?->toIso8601String(),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Api\PredictionRequest;
|
||||
use App\Services\NationalFuelPredictionService;
|
||||
use App\Services\PlanFeatures;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class PredictionController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly NationalFuelPredictionService $predictionService,
|
||||
) {}
|
||||
|
||||
public function index(PredictionRequest $request): JsonResponse
|
||||
{
|
||||
$lat = $request->filled('lat') ? (float) $request->input('lat') : null;
|
||||
$lng = $request->filled('lng') ? (float) $request->input('lng') : null;
|
||||
|
||||
$result = $this->predictionService->predict($lat, $lng);
|
||||
|
||||
$user = $request->user();
|
||||
$canSeeFull = $user !== null && PlanFeatures::for($user)->can('ai_predictions');
|
||||
|
||||
if (! $canSeeFull) {
|
||||
return response()->json([
|
||||
'fuel_type' => $result['fuel_type'],
|
||||
'predicted_direction' => $result['predicted_direction'],
|
||||
'tier_locked' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,9 @@ use App\Http\Requests\Api\NearbyStationsRequest;
|
||||
use App\Http\Resources\Api\StationResource;
|
||||
use App\Models\Search;
|
||||
use App\Models\Station;
|
||||
use App\Models\User;
|
||||
use App\Services\NationalFuelPredictionService;
|
||||
use App\Services\PlanFeatures;
|
||||
use App\Services\PostcodeService;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@@ -16,7 +19,10 @@ use Illuminate\Validation\ValidationException;
|
||||
|
||||
class StationController extends Controller
|
||||
{
|
||||
public function __construct(private readonly PostcodeService $postcodeService) {}
|
||||
public function __construct(
|
||||
private readonly PostcodeService $postcodeService,
|
||||
private readonly NationalFuelPredictionService $predictionService,
|
||||
) {}
|
||||
|
||||
public function index(NearbyStationsRequest $request): JsonResponse
|
||||
{
|
||||
@@ -115,6 +121,31 @@ class StationController extends Controller
|
||||
'outdated' => (int) $reliabilityCounts->get(PriceReliability::Outdated->value, 0),
|
||||
],
|
||||
],
|
||||
'prediction' => $this->predictionFor($request->user(), $lat, $lng),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the prediction payload for embedding in the search response.
|
||||
* Free/guest users get a stripped teaser; users with the ai_predictions
|
||||
* feature get the full multi-signal payload.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function predictionFor(?User $user, float $lat, float $lng): array
|
||||
{
|
||||
$result = $this->predictionService->predict($lat, $lng);
|
||||
|
||||
$canSeeFull = $user !== null && PlanFeatures::for($user)->can('ai_predictions');
|
||||
|
||||
if (! $canSeeFull) {
|
||||
return [
|
||||
'fuel_type' => $result['fuel_type'],
|
||||
'predicted_direction' => $result['predicted_direction'],
|
||||
'tier_locked' => true,
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
||||
use App\Enums\PlanTier;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Cashier\Checkout;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class BillingController extends Controller
|
||||
@@ -12,7 +13,7 @@ class BillingController extends Controller
|
||||
/**
|
||||
* Redirect the user to a Stripe Checkout session for the requested plan + cadence.
|
||||
*/
|
||||
public function checkout(Request $request, string $tier, string $cadence): Response|RedirectResponse
|
||||
public function checkout(Request $request, string $tier, string $cadence): Response|RedirectResponse|Checkout
|
||||
{
|
||||
abort_unless(in_array($tier, [PlanTier::Basic->value, PlanTier::Plus->value, PlanTier::Pro->value], true), 404);
|
||||
abort_unless(in_array($cadence, ['monthly', 'annual'], true), 404);
|
||||
|
||||
Reference in New Issue
Block a user