File: /home/xedaptot/ai.naniguide.com/app/Http/Controllers/Refactor/Ads/AdPlatformController.php
<?php
namespace App\Http\Controllers\Refactor\Ads;
use App\Http\Controllers\Controller;
use App\Model\AdPlatform;
use App\Services\Ads\AdPlatformManager;
use App\Services\Ads\AdActivityLogger;
use App\Services\Ads\AdPlatformException;
use App\Services\Ads\AdCredentialResolver;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class AdPlatformController extends Controller
{
protected AdPlatformManager $manager;
public function __construct(AdPlatformManager $manager)
{
$this->manager = $manager;
}
/**
* Platform connections index page.
*/
public function index(Request $request)
{
$customer = $request->user()->customer;
$provisioningStatus = [];
foreach (AdPlatform::PLATFORMS as $platform) {
$provisioningStatus[$platform] = AdCredentialResolver::isConfigured($platform);
}
return view('refactor.pages.ads.platforms.index', [
'platforms' => $customer->local()->adPlatforms()->active()->get(),
'availablePlatforms' => AdPlatform::PLATFORMS,
'platformLabels' => AdPlatform::PLATFORM_LABELS,
'provisioningStatus' => $provisioningStatus,
]);
}
/**
* AJAX listing partial.
*/
public function listing(Request $request)
{
$customer = $request->user()->customer;
$platforms = $customer->local()->adPlatforms()
->search($request->keyword)
->orderBy('created_at', 'desc')
->get();
return view('refactor.pages.ads.platforms._list', [
'platforms' => $platforms,
]);
}
/**
* Initiate OAuth connection flow.
*/
public function connect(Request $request, string $platform)
{
if (!in_array($platform, AdPlatform::PLATFORMS)) {
return redirect()->route('refactor.ads.platforms.index')
->with('error', trans('refactor/ads_platforms.invalid_platform'));
}
// Phase N — fail-fast if admin hasn't configured this platform yet.
// Return friendly empty state instead of letting an empty client_id hit OAuth URL.
if (!app(\App\Services\Ads\AdPlatformManager::class)->isMockMode() && !AdCredentialResolver::isConfigured($platform)) {
return redirect()->route('refactor.ads.platforms.index')
->with('error', trans('refactor/ads_platforms.not_configured', [
'platform' => AdPlatform::PLATFORM_LABELS[$platform],
]));
}
// Plan quota gate — canAccept() encapsulates the 4-state limit semantic.
$customer = $request->user()->customer;
$quotas = app(\App\Services\Plans\Quotas\QuotasService::class);
$qKey = \App\Services\Plans\Quotas\QuotaKey::MAX_ADS_PLATFORMS;
if (! $quotas->canAccept($customer, $qKey, 1)) {
// Distinguish "not on plan" (no row) from "cap reached".
$errKey = $quotas->limit($customer, $qKey) === false
? 'refactor/ads_platforms.not_on_plan'
: 'refactor/ads_platforms.quota_exceeded';
return redirect()->route('refactor.ads.platforms.index')
->with('error', trans($errKey));
}
// Generate CSRF state parameter
$state = Str::random(40);
session(['ads_oauth_state' => $state, 'ads_oauth_platform' => $platform]);
$redirectUri = route('refactor.ads.platforms.callback', ['platform' => $platform]);
$adapter = $this->manager->adapter($platform);
// Phase N — adapter is single source of truth for default scopes
$scopes = $adapter->getDefaultScopes();
$authUrl = $adapter->getAuthUrl($redirectUri, $scopes, $state);
return redirect($authUrl);
}
/**
* OAuth callback handler.
*/
public function callback(Request $request, string $platform)
{
// Verify CSRF state
$expectedState = session('ads_oauth_state');
$receivedState = $request->input('state');
if (!$expectedState || $expectedState !== $receivedState) {
return redirect()->route('refactor.ads.platforms.index')
->with('error', trans('refactor/ads_platforms.oauth_state_mismatch'));
}
session()->forget(['ads_oauth_state', 'ads_oauth_platform']);
if ($request->has('error')) {
return redirect()->route('refactor.ads.platforms.index')
->with('error', trans('refactor/ads_platforms.oauth_denied', ['message' => $request->input('error_description', $request->input('error'))]));
}
$code = $request->input('code') ?? $request->input('auth_code');
if (!$code) {
return redirect()->route('refactor.ads.platforms.index')
->with('error', trans('refactor/ads_platforms.oauth_no_code'));
}
try {
$redirectUri = route('refactor.ads.platforms.callback', ['platform' => $platform]);
$adapter = $this->manager->adapter($platform);
$tokenResult = $adapter->handleCallback($code, $redirectUri);
$customer = $request->user()->customer;
$adPlatform = $this->manager->connect($customer->local(), $platform, $tokenResult);
return redirect()->route('refactor.ads.platforms.index')
->with('success', trans('refactor/ads_platforms.connected', ['platform' => $adPlatform->platform_label]));
} catch (AdPlatformException $e) {
AdActivityLogger::log(
entity: new AdPlatform(['customer_id' => $request->user()->customer->id, 'id' => 0]),
action: 'platform.connect_failed',
status: 'error',
title: AdPlatform::PLATFORM_LABELS[$platform] . ' connection failed',
errorCode: $e->getPlatformCode(),
errorMessage: $e->getMessage(),
resolutionHint: $e->getResolutionHint(),
platform: $platform,
);
return redirect()->route('refactor.ads.platforms.index')
->with('error', $e->getMessage());
}
}
/**
* Disconnect a platform.
*/
public function disconnect(Request $request, string $uid)
{
$customer = $request->user()->customer;
$platform = $customer->local()->adPlatforms()->where('uid', $uid)->firstOrFail();
$this->manager->disconnect($platform);
return response()->json([
'status' => 'success',
'message' => trans('refactor/ads_platforms.disconnected', ['platform' => $platform->platform_label]),
]);
}
/**
* Run health check on a platform.
*/
public function healthCheck(Request $request, string $uid)
{
$customer = $request->user()->customer;
$platform = $customer->local()->adPlatforms()->where('uid', $uid)->firstOrFail();
$result = $this->manager->runHealthCheck($platform);
return response()->json([
'status' => $result->status,
'message' => $result->message,
'health_status' => $platform->fresh()->health_status,
]);
}
/**
* Refresh platform token manually.
*/
public function refresh(Request $request, string $uid)
{
$customer = $request->user()->customer;
$platform = $customer->local()->adPlatforms()->where('uid', $uid)->firstOrFail();
$this->manager->refreshIfExpired($platform);
return response()->json([
'status' => 'success',
'message' => trans('refactor/ads_platforms.token_refreshed'),
]);
}
/**
* Get ad accounts for a platform.
*/
public function accounts(Request $request, string $uid)
{
$customer = $request->user()->customer;
$platform = $customer->local()->adPlatforms()->where('uid', $uid)->firstOrFail();
return response()->json([
'accounts' => $platform->accounts()->get()->map(function ($account) {
return [
'uid' => $account->uid,
'name' => $account->name,
'currency' => $account->currency,
'is_default' => $account->is_default,
];
})->values(),
]);
}
/**
* Set default ad account for a platform.
*/
public function setDefaultAccount(Request $request, string $uid)
{
$customer = $request->user()->customer;
$platform = $customer->local()->adPlatforms()->where('uid', $uid)->firstOrFail();
$accountUid = $request->input('account_uid');
$account = $platform->accounts()->where('uid', $accountUid)->firstOrFail();
// Clear existing default
$platform->accounts()->update(['is_default' => false]);
$account->update(['is_default' => true]);
return response()->json([
'status' => 'success',
'message' => trans('refactor/ads_platforms.default_account_set', ['name' => $account->name]),
]);
}
}