Source path: app/Http/Controllers/OAuthController.php
Extracted from supplied specification. Review before copying into application.

<?php
namespace App\Http\Controllers;

use App\Models\SocialAccount;
use App\Services\Publishers\PublisherFactory;
use App\Support\Roles;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

class OAuthController extends Controller
{
    public function redirect(Request $r, string $platform)
    {
        Roles::require($r, 'admin');
        $c = config("social.$platform");

        $state = Str::random(40);
        $verifier = Str::random(64);
        $r->session()->put("oauth.$state", [
            'platform' => $platform, 'org' => app('currentOrg')->id, 'verifier' => $verifier,
        ]);

        $params = [
            $c['id_param'] => $c['client_id'],
            'redirect_uri' => url("/connect/$platform/callback"),
            'response_type' => 'code',
            'scope' => implode($c['scope_sep'], $c['scopes']),
            'state' => $state,
        ] + $c['extra'];

        if ($c['pkce']) {
            $params['code_challenge'] = rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '=');
            $params['code_challenge_method'] = 'S256';
        }

        return redirect($c['auth_url'] . '?' . http_build_query($params));
    }

    public function callback(Request $r, string $platform, PublisherFactory $factory)
    {
        $saved = $r->session()->pull('oauth.' . $r->query('state'));
        abort_if(!$saved || $saved['platform'] !== $platform || !$r->query('code'), 400, 'Invalid OAuth state.');

        $c = config("social.$platform");
        $form = [
            'grant_type' => 'authorization_code',
            'code' => $r->query('code'),
            'redirect_uri' => url("/connect/$platform/callback"),
            $c['id_param'] => $c['client_id'],
        ];
        if ($c['pkce']) $form['code_verifier'] = $saved['verifier'];

        $req = Http::asForm();
        if ($c['auth_style'] === 'basic') $req = $req->withBasicAuth($c['client_id'], $c['client_secret']);
        else $form['client_secret'] = $c['client_secret'];

        $tok = $req->post($c['token_url'], $form)->throw()->json();

        foreach ($factory->for($platform)->discoverAccounts($tok['access_token']) as $a) {
            SocialAccount::withoutGlobalScopes()->updateOrCreate(
                ['organization_id' => $saved['org'], 'platform' => $platform, 'external_id' => $a['id']],
                [
                    'user_id' => auth()->id(), 'added_by' => auth()->id(), 'name' => $a['name'],
                    'access_token' => $tok['access_token'],
                    'refresh_token' => $tok['refresh_token'] ?? null,
                    'token_expires_at' => isset($tok['expires_in']) ? now()->addSeconds((int) $tok['expires_in']) : null,
                    'is_active' => 1,
                ]
            );
        }

        return redirect('/posts')->with('status', ucfirst($platform) . ' connected.');
    }
}

Routes (inside the auth + org group):

php
Route::get('/connect/{platform}', [OAuthController::class, 'redirect'])->where('platform', 'x|linkedin|tiktok|youtube');
Route::get('/connect/{platform}/callback', [OAuthController::class, 'callback'])->where('platform', 'x|linkedin|tiktok|youtube');
Route::post('/ai/caption', [AiController::class, 'caption']);
Route::post('/ai/providers', [AiController::class, 'saveProvider']);

Register each callback URL, APP_URL/connect/{platform}/callback, in the platform’s developer portal.

Publisher base + interface

Update Publisher.php and add discoverAccounts (return [] in the Facebook/Instagram classes, since the Meta controller handles them):

php
public function discoverAccounts(string $accessToken): array; // [['id'=>..., 'name'=>...]]
