File: /home/xedaptot/be.naniguide.com/app/Http/Controllers/Refactor/Admin/AdminAdsSettingsController.php
<?php
namespace App\Http\Controllers\Refactor\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Refactor\Ads\AdsSettingsUpdateRequest;
use App\Model\AdPlatform;
use App\Model\Setting;
use App\Services\Ads\AdCredentialResolver;
use App\Services\Ads\AdPlatformManager;
use App\Services\Ads\TestConnectionResult;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Phase N — Admin Self-Service Ads Platform Configuration.
*
* Lets SaaS admins configure OAuth app credentials via a step-by-step wizard
* instead of editing .env. Provides a live "Test Connection" button that
* validates credentials against each platform's API without requiring user OAuth.
*/
class AdminAdsSettingsController extends Controller
{
public function __construct(private readonly AdPlatformManager $manager)
{
}
/**
* Show the 5-step wizard.
*/
public function show(Request $request)
{
if (!$this->canManage($request)) {
return $this->notAuthorized();
}
$credentials = [];
$sources = [];
foreach (AdPlatform::PLATFORMS as $platform) {
$credentials[$platform] = AdCredentialResolver::all($platform);
$sources[$platform] = AdCredentialResolver::source($platform);
}
$globals = [
'mock_mode' => (string) (Setting::get('ads.mock_mode') ?? (config('ads.mock_mode') ? 'yes' : 'no')),
'metrics_sync_interval' => (int) (Setting::get('ads.metrics_sync_interval') ?? config('ads.metrics_sync_interval', 15)),
'rate_limit_buffer' => (int) (Setting::get('ads.rate_limit_buffer') ?? 80),
];
return view('refactor.pages.admin.ads.settings', [
'credentials' => $credentials,
'sources' => $sources,
'globals' => $globals,
'platforms' => AdPlatform::PLATFORMS,
'platformLabels' => AdPlatform::PLATFORM_LABELS,
'requiredKeys' => AdCredentialResolver::REQUIRED_KEYS,
]);
}
/**
* Save wizard input to settings table.
*/
public function update(AdsSettingsUpdateRequest $request)
{
if (!$this->canManage($request)) {
return $this->notAuthorized();
}
$map = [
'ads_meta_app_id' => 'ads.meta.app_id',
'ads_meta_app_secret' => 'ads.meta.app_secret',
'ads_google_client_id' => 'ads.google.client_id',
'ads_google_client_secret' => 'ads.google.client_secret',
'ads_google_developer_token' => 'ads.google.developer_token',
'ads_tiktok_app_id' => 'ads.tiktok.app_id',
'ads_tiktok_app_secret' => 'ads.tiktok.app_secret',
'ads_linkedin_client_id' => 'ads.linkedin.client_id',
'ads_linkedin_client_secret' => 'ads.linkedin.client_secret',
'ads_mock_mode' => 'ads.mock_mode',
'ads_metrics_sync_interval' => 'ads.metrics_sync_interval',
'ads_rate_limit_buffer' => 'ads.rate_limit_buffer',
];
foreach ($map as $input => $settingKey) {
if ($request->filled($input)) {
Setting::set($settingKey, $request->input($input));
}
}
if ($request->ajax() || $request->wantsJson()) {
return response()->json([
'status' => 'success',
'message' => trans('refactor/ads_admin_settings.saved'),
]);
}
$request->session()->flash('alert-success', trans('refactor/ads_admin_settings.saved'));
return redirect()->route('refactor.admin.ads.settings.show');
}
/**
* AJAX: test credentials for a single platform WITHOUT persisting.
* Accepts POST body with draft credentials so admin can test before save.
*/
public function testConnection(Request $request, string $platform): JsonResponse
{
if (!$this->canManage($request)) {
return response()->json(['status' => 'error', 'message' => 'Not authorized'], 403);
}
if (!in_array($platform, AdPlatform::PLATFORMS, true)) {
return response()->json([
'status' => TestConnectionResult::STATUS_ERROR,
'message' => 'Unknown platform.',
], 422);
}
// Collect draft credentials from request body. Fall back to persisted settings if not in body.
$draftCreds = [];
foreach (AdCredentialResolver::requiredKeys($platform) as $key) {
$input = "ads_{$platform}_{$key}";
$draftCreds[$key] = $request->input($input) ?: AdCredentialResolver::resolve($platform, $key);
}
$adapter = $this->manager->adapter($platform);
$result = $adapter->testAppCredentials($draftCreds);
return response()->json([
'status' => $result->status,
'message' => $result->message,
'app_review_status' => $result->appReviewStatus,
'is_dev_mode' => $result->isDevMode(),
'resolution_hint' => $result->resolutionHint,
]);
}
/**
* AJAX: get the latest app review status for a platform (cached).
*/
public function reviewStatus(Request $request, string $platform): JsonResponse
{
if (!$this->canManage($request)) {
return response()->json(['status' => 'error', 'message' => 'Not authorized'], 403);
}
if (!in_array($platform, AdPlatform::PLATFORMS, true)) {
return response()->json(['status' => 'error', 'message' => 'Unknown platform.'], 422);
}
if (!AdCredentialResolver::isConfigured($platform)) {
return response()->json([
'configured' => false,
'app_review_status' => null,
]);
}
$adapter = $this->manager->adapter($platform);
$result = $adapter->testAppCredentials(AdCredentialResolver::all($platform));
return response()->json([
'configured' => true,
'app_review_status' => $result->appReviewStatus,
'is_dev_mode' => $result->isDevMode(),
'message' => $result->message,
]);
}
private function canManage(Request $request): bool
{
$user = $request->user();
if (!$user) {
return false;
}
/** @var \App\Model\Admin|null $admin */
$admin = $user->admin()->first();
if (!$admin) {
return false;
}
// Reuse setting_general permission; admins who can manage settings can manage ads platform creds.
return $admin->getPermission('setting_general') === 'yes';
}
}