trypost/app/Http/Controllers/Auth/YouTubeController.php
Paulo Castellano cbea8394fc feat: localize OAuth popup callback messages across 12 controllers
User reported the social-account OAuth callback popup ('Threads account
connected!') stayed in English regardless of the active locale. The
hardcoded message was wired through SocialController and 11 platform
controllers (Bluesky, Facebook, Instagram, InstagramFacebook, LinkedIn,
LinkedInPage, Mastodon, Pinterest, Threads, TikTok, YouTube), plus the
Blade view that the popup renders.

Adds an accounts.popup_callback i18n block (en/pt-BR/es) covering:
- The popup chrome (title, closing/close-now status text).
- Generic success/reconnect messages (one shared 'Account connected!'
  / 'Account reconnected!' line — the popup already shows a checkmark
  and lives for ~2s so platform-specific wording wasn't pulling weight).
- Error variants (account/page/channel) and contextual edge cases
  (page not found, no Facebook pages, no YouTube channels, etc.).

Updates every popupCallback() callsite to read from these keys, plus
the Blade view's title and submessage. Regenerates the JSON locale
bundle so laravel-vue-i18n stays in sync.
2026-05-07 14:03:52 -03:00

314 lines
12 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Http\Controllers\Auth;
use App\Enums\SocialAccount\Platform as SocialPlatform;
use App\Enums\SocialAccount\Status;
use App\Models\Workspace;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Inertia\Inertia;
use Laravel\Socialite\Facades\Socialite;
use Symfony\Component\HttpFoundation\Response;
class YouTubeController extends SocialController
{
protected string $driver = 'google';
protected SocialPlatform $platform = SocialPlatform::YouTube;
protected array $scopes = [
'https://www.googleapis.com/auth/youtube.upload',
'https://www.googleapis.com/auth/youtube.readonly',
'https://www.googleapis.com/auth/youtube.force-ssl',
'https://www.googleapis.com/auth/yt-analytics.readonly',
];
public function connect(Request $request): Response|RedirectResponse
{
$this->ensurePlatformEnabled();
$workspace = $request->user()->currentWorkspace;
if (! $workspace) {
return redirect()->route('app.workspaces.create');
}
$this->authorize('manageAccounts', $workspace);
$this->ensureSocialAccountLimit($workspace);
session([
'social_connect_workspace' => $workspace->id,
'social_reconnect_id' => null,
'social_connect_onboarding' => $request->boolean('onboarding'),
]);
return $this->redirectToGoogle();
}
public function callback(Request $request): View|RedirectResponse
{
$workspaceId = session('social_connect_workspace');
if (! $workspaceId) {
return $this->popupCallback(false, __('accounts.popup_callback.session_expired'), $this->platform->value);
}
$workspace = Workspace::find($workspaceId);
if (! $workspace || ! $request->user()->can('manageAccounts', $workspace)) {
return $this->popupCallback(false, __('accounts.popup_callback.workspace_not_found'), $this->platform->value);
}
try {
$socialUser = Socialite::driver($this->driver)->user();
// Fetch the channels the user authorized
$channels = $this->fetchChannels($socialUser->token);
if (empty($channels)) {
return $this->popupCallback(false, __('accounts.popup_callback.no_youtube_channels'), $this->platform->value);
}
// If only one channel, connect directly (most common case)
if (count($channels) === 1) {
$channel = $channels[0];
$avatarPath = uploadFromUrl(data_get($channel, 'thumbnail'));
$workspace->socialAccounts()->updateOrCreate(
[
'platform' => $this->platform->value,
'platform_user_id' => data_get($channel, 'id'),
],
[
'username' => ltrim(data_get($channel, 'custom_url', data_get($channel, 'id')), '@'),
'display_name' => data_get($channel, 'title'),
'avatar_url' => $avatarPath,
'access_token' => $socialUser->token,
'refresh_token' => $socialUser->refreshToken,
'token_expires_at' => $socialUser->expiresIn ? now()->addSeconds($socialUser->expiresIn) : null,
'scopes' => $this->scopes,
'status' => Status::Connected,
'error_message' => null,
'disconnected_at' => null,
'meta' => [
'channel_id' => data_get($channel, 'id'),
'google_user_id' => $socialUser->getId(),
],
],
);
return $this->popupCallback(true, __('accounts.popup_callback.connected'), $this->platform->value);
}
// Multiple channels - store data and show selection screen
session([
'youtube_oauth' => [
'access_token' => $socialUser->token,
'refresh_token' => $socialUser->refreshToken,
'expires_in' => $socialUser->expiresIn,
'user_id' => $socialUser->getId(),
],
]);
return redirect()->route('app.social.youtube.select-channel');
} catch (\Exception $e) {
Log::error('YouTube OAuth Error', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
return $this->popupCallback(false, __('accounts.popup_callback.error_connecting'), $this->platform->value);
}
}
public function selectChannel(Request $request)
{
$oauthData = session('youtube_oauth');
$workspaceId = session('social_connect_workspace');
if (! $oauthData || ! $workspaceId) {
session()->flash('flash.banner', __('accounts.flash.session_expired'));
session()->flash('flash.bannerStyle', 'danger');
return redirect()->route('app.accounts');
}
$workspace = Workspace::find($workspaceId);
if (! $workspace) {
session()->flash('flash.banner', __('accounts.flash.workspace_not_found'));
session()->flash('flash.bannerStyle', 'danger');
return redirect()->route('app.accounts');
}
// Fetch YouTube channels
$channels = $this->fetchChannels(data_get($oauthData, 'access_token'));
if (empty($channels)) {
$redirectRoute = $this->getRedirectRoute();
$this->forgetSocialConnectSession();
session()->forget('youtube_oauth');
session()->flash('flash.banner', __('accounts.flash.no_youtube_channels'));
session()->flash('flash.bannerStyle', 'danger');
return redirect()->route($redirectRoute);
}
return inertia('accounts/YouTubeChannelSelect', [
'workspace' => $workspace,
'channels' => $channels,
]);
}
public function select(Request $request): View
{
$request->validate([
'channel_id' => 'required|string',
]);
$oauthData = session('youtube_oauth');
$workspaceId = session('social_connect_workspace');
if (! $oauthData || ! $workspaceId) {
return $this->popupCallback(false, __('accounts.popup_callback.session_expired'), $this->platform->value);
}
$workspace = Workspace::find($workspaceId);
if (! $workspace || ! $request->user()->can('manageAccounts', $workspace)) {
return $this->popupCallback(false, __('accounts.popup_callback.workspace_not_found'), $this->platform->value);
}
try {
$channels = $this->fetchChannels(data_get($oauthData, 'access_token'));
$selectedChannel = collect($channels)->firstWhere('id', $request->channel_id);
if (! $selectedChannel) {
return $this->popupCallback(false, __('accounts.popup_callback.channel_not_found'), $this->platform->value);
}
$avatarPath = uploadFromUrl(data_get($selectedChannel, 'thumbnail'));
$reconnectId = data_get($oauthData, 'reconnect_id', null);
if ($reconnectId) {
// Reconnect existing account
$existingAccount = $workspace->socialAccounts()->find($reconnectId);
if ($existingAccount) {
$existingAccount->update([
'platform_user_id' => data_get($selectedChannel, 'id'),
'username' => ltrim(data_get($selectedChannel, 'custom_url', data_get($selectedChannel, 'id')), '@'),
'display_name' => data_get($selectedChannel, 'title'),
'avatar_url' => $avatarPath,
'access_token' => data_get($oauthData, 'access_token'),
'refresh_token' => data_get($oauthData, 'refresh_token'),
'token_expires_at' => data_get($oauthData, 'expires_in') ? now()->addSeconds(data_get($oauthData, 'expires_in')) : null,
'scopes' => $this->scopes,
'meta' => [
'channel_id' => data_get($selectedChannel, 'id'),
'google_user_id' => data_get($oauthData, 'user_id'),
],
]);
$existingAccount->markAsConnected();
session()->forget(['youtube_oauth', 'social_reconnect_id']);
return $this->popupCallback(true, __('accounts.popup_callback.reconnected'), $this->platform->value);
}
}
$workspace->socialAccounts()->updateOrCreate(
[
'platform' => $this->platform->value,
'platform_user_id' => data_get($selectedChannel, 'id'),
],
[
'username' => ltrim(data_get($selectedChannel, 'custom_url', data_get($selectedChannel, 'id')), '@'),
'display_name' => data_get($selectedChannel, 'title'),
'avatar_url' => $avatarPath,
'access_token' => data_get($oauthData, 'access_token'),
'refresh_token' => data_get($oauthData, 'refresh_token'),
'token_expires_at' => data_get($oauthData, 'expires_in') ? now()->addSeconds(data_get($oauthData, 'expires_in')) : null,
'scopes' => $this->scopes,
'status' => Status::Connected,
'error_message' => null,
'disconnected_at' => null,
'meta' => [
'channel_id' => data_get($selectedChannel, 'id'),
'google_user_id' => data_get($oauthData, 'user_id'),
],
],
);
session()->forget(['youtube_oauth', 'social_reconnect_id']);
return $this->popupCallback(true, __('accounts.popup_callback.connected'), $this->platform->value);
} catch (\Exception $e) {
Log::error('YouTube channel selection error', [
'error' => $e->getMessage(),
]);
return $this->popupCallback(false, __('accounts.popup_callback.error_connecting_channel'), $this->platform->value);
}
}
private function redirectToGoogle(): Response
{
return Inertia::location(
Socialite::driver($this->driver)
->scopes($this->scopes)
->with([
'access_type' => 'offline',
'prompt' => 'consent',
'include_granted_scopes' => 'true',
])
->redirect()
->getTargetUrl()
);
}
private function fetchChannels(string $accessToken): array
{
try {
$response = Http::withToken($accessToken)
->get(config('trypost.platforms.youtube.data_api').'/channels', [
'part' => 'snippet,contentDetails,statistics',
'mine' => 'true',
]);
if ($response->failed()) {
Log::error('YouTube channels fetch failed', [
'status' => $response->status(),
'body' => $response->body(),
]);
return [];
}
$data = $response->json();
return collect(data_get($data, 'items', []))->map(fn ($channel) => [
'id' => data_get($channel, 'id'),
'title' => data_get($channel, 'snippet.title'),
'description' => data_get($channel, 'snippet.description', ''),
'thumbnail' => data_get($channel, 'snippet.thumbnails.default.url'),
'custom_url' => data_get($channel, 'snippet.customUrl'),
'subscriber_count' => data_get($channel, 'statistics.subscriberCount', 0),
])->toArray();
} catch (\Exception $e) {
Log::error('YouTube channels fetch error', [
'error' => $e->getMessage(),
]);
return [];
}
}
}