2026-01-15 17:24:39 +00:00
|
|
|
<?php
|
|
|
|
|
|
refactor: settings redesign, Spanish translations, language system, strict_types
Settings pages:
- Redesign layout to match Sendkit (max-w-4xl, space-y-12, Separator sections)
- Merge Members page into Workspace settings with Table, invite Dialog, ConfirmDeleteModal
- Add workspace logo upload/delete routes and controller methods
- Translate all hardcoded strings in Workspace.vue modals
Language system:
- Drop languages table, replace language_id FK with locale string column on users
- Create config/languages.php for available languages and default locale
- Add Spanish (es) translations (13 files)
- Simplify HandleInertiaRequests, ProfileController, RegisteredUserController
Code quality:
- Add declare(strict_types=1) to all PHP files
- Fix MastodonPublisher using wrong attribute (filename -> original_filename)
- Fix HasMediaTest for new has_photo/photo_url accessors
- Fix PublishToSocialPlatformTest type error revealed by strict_types
- Remove orphaned Language model from AppServiceProvider morph map
- Update User TypeScript interface (has_photo, photo_url, locale)
- Eager load media relation on workspaces to prevent N+1
- Add 8 new tests for workspace logo upload/delete
- Update workspace settings test to assert members/invitations props
All 710 tests passing.
2026-03-30 03:20:43 +00:00
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
2026-01-15 17:24:39 +00:00
|
|
|
namespace App\Http\Controllers\Auth;
|
|
|
|
|
|
2026-01-17 17:44:37 +00:00
|
|
|
use App\Enums\SocialAccount\Platform as SocialPlatform;
|
|
|
|
|
use App\Enums\SocialAccount\Status;
|
2026-06-22 00:28:47 +00:00
|
|
|
use App\Exceptions\SocialAccount\NetworkAlreadyConnectedException;
|
2026-01-15 17:24:39 +00:00
|
|
|
use App\Models\Workspace;
|
|
|
|
|
use Illuminate\Http\RedirectResponse;
|
|
|
|
|
use Illuminate\Http\Request;
|
2026-03-29 22:24:28 +00:00
|
|
|
use Illuminate\Support\Facades\Http;
|
2026-01-15 17:24:39 +00:00
|
|
|
use Illuminate\Support\Facades\Log;
|
2026-03-29 22:24:28 +00:00
|
|
|
use Inertia\Inertia;
|
2026-06-25 16:54:16 +00:00
|
|
|
use Inertia\Response as InertiaResponse;
|
2026-01-15 17:24:39 +00:00
|
|
|
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',
|
2026-04-14 16:10:27 +00:00
|
|
|
'https://www.googleapis.com/auth/yt-analytics.readonly',
|
2026-01-15 17:24:39 +00:00
|
|
|
];
|
|
|
|
|
|
2026-06-25 17:30:15 +00:00
|
|
|
public function connect(Request $request): Response
|
2026-01-15 17:24:39 +00:00
|
|
|
{
|
2026-01-16 15:36:52 +00:00
|
|
|
$this->ensurePlatformEnabled();
|
2026-01-17 02:46:30 +00:00
|
|
|
|
|
|
|
|
$workspace = $request->user()->currentWorkspace;
|
|
|
|
|
|
2026-01-15 17:24:39 +00:00
|
|
|
$this->authorize('manageAccounts', $workspace);
|
|
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
session([
|
|
|
|
|
'social_connect_workspace' => $workspace->id,
|
2026-04-14 22:48:35 +00:00
|
|
|
'social_reconnect_id' => null,
|
2026-01-17 02:46:30 +00:00
|
|
|
]);
|
2026-01-15 17:24:39 +00:00
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
return $this->redirectToGoogle();
|
2026-01-15 17:24:39 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-25 16:54:16 +00:00
|
|
|
public function callback(Request $request): InertiaResponse|RedirectResponse
|
2026-01-15 17:24:39 +00:00
|
|
|
{
|
|
|
|
|
$workspaceId = session('social_connect_workspace');
|
|
|
|
|
|
|
|
|
|
if (! $workspaceId) {
|
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 17:03:52 +00:00
|
|
|
return $this->popupCallback(false, __('accounts.popup_callback.session_expired'), $this->platform->value);
|
2026-01-15 17:24:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$workspace = Workspace::find($workspaceId);
|
|
|
|
|
|
|
|
|
|
if (! $workspace || ! $request->user()->can('manageAccounts', $workspace)) {
|
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 17:03:52 +00:00
|
|
|
return $this->popupCallback(false, __('accounts.popup_callback.workspace_not_found'), $this->platform->value);
|
2026-01-15 17:24:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$socialUser = Socialite::driver($this->driver)->user();
|
|
|
|
|
|
|
|
|
|
// Fetch the channels the user authorized
|
|
|
|
|
$channels = $this->fetchChannels($socialUser->token);
|
|
|
|
|
|
|
|
|
|
if (empty($channels)) {
|
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 17:03:52 +00:00
|
|
|
return $this->popupCallback(false, __('accounts.popup_callback.no_youtube_channels'), $this->platform->value);
|
2026-01-15 17:24:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If only one channel, connect directly (most common case)
|
|
|
|
|
if (count($channels) === 1) {
|
|
|
|
|
$channel = $channels[0];
|
2026-03-31 03:40:18 +00:00
|
|
|
$avatarPath = uploadFromUrl(data_get($channel, 'thumbnail'));
|
2026-01-15 17:24:39 +00:00
|
|
|
|
2026-04-15 12:46:18 +00:00
|
|
|
$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(),
|
|
|
|
|
],
|
2026-01-15 17:24:39 +00:00
|
|
|
],
|
2026-04-15 12:46:18 +00:00
|
|
|
);
|
2026-01-15 17:24:39 +00:00
|
|
|
|
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 17:03:52 +00:00
|
|
|
return $this->popupCallback(true, __('accounts.popup_callback.connected'), $this->platform->value);
|
2026-01-15 17:24:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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(),
|
|
|
|
|
],
|
|
|
|
|
]);
|
|
|
|
|
|
2026-03-29 22:24:28 +00:00
|
|
|
return redirect()->route('app.social.youtube.select-channel');
|
2026-06-22 00:28:47 +00:00
|
|
|
} catch (NetworkAlreadyConnectedException) {
|
|
|
|
|
return $this->popupCallback(false, __('accounts.popup_callback.network_taken'), $this->platform->value);
|
2026-01-15 17:24:39 +00:00
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
Log::error('YouTube OAuth Error', [
|
|
|
|
|
'error' => $e->getMessage(),
|
|
|
|
|
'trace' => $e->getTraceAsString(),
|
|
|
|
|
]);
|
|
|
|
|
|
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 17:03:52 +00:00
|
|
|
return $this->popupCallback(false, __('accounts.popup_callback.error_connecting'), $this->platform->value);
|
2026-01-15 17:24:39 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Fix Facebook Page connect pagination (#212) (#253)
* Fix Facebook and Instagram-via-Facebook Page connect pagination.
Follow Graph API paging.next on /me/accounts so authorized non-first Pages are found and multi-Page accounts get the picker instead of silently connecting the first result.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Paginate Meta accounts until paging.next is exhausted.
Drop the artificial 50-page cap and stop only when there is no next URL, or the same request URL repeats (broken pagination loop).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Redact tokens in Graph pagination logs and harden test coverage.
Cover happy-path and failure cases for Meta /me/accounts pagination, including mid-loop failures, invalid paging.next, and Instagram pages without a linked IG account.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fail closed on incomplete Meta accounts pagination.
If a later /me/accounts page fails after earlier pages succeeded, throw instead of returning a truncated list that could auto-connect the wrong Page. Also revert the IG detail timeout that could wipe the whole connect list.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify Graph pagination helpers and page fetchers.
Bake the first request query into the URL, drop requestKey, and let IncompleteGraphPaginationException bubble from the controllers without catch/rethrow noise.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Move incomplete pagination exception under Social\Meta.
Colocate it with GraphPaginator so the Meta scope is clear from the namespace instead of a generic Social exception name.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Rename pagination exception to IncompleteMetaGraphPaginationException.
Keep it under Exceptions/Social with Meta in the class name instead of moving it into Services.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Make GraphPaginator results explicit before mapping pages.
Assign the paginated accounts to a variable first so the Facebook and Instagram-via-Facebook fetchers read more clearly.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Build Meta Graph pagination URLs with Laravel Uri.
Replace manual http_build_query concatenation with Uri::of()->withQuery().
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use Laravel HTTP and Uri helpers in Meta Graph pagination.
Prefer response collect/json key access, filled(), and Uri path parsing over manual array and parse_url handling.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify graphVersion using Uri path and str().
Drop basename and native string casts; Uri::path() already yields the Graph API version segment.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Drop unnecessary str() around graph API config.
Uri: :of() already accepts the string returned by config().
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify GraphPaginator with Laravel helpers.
Consolidate failure handling via abort(), and use collect, when, throw_if, and Uri::value() for a shorter pagination loop.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Refactor social OAuth page/channel selection handling. Update selectPage and selectChannel methods in Facebook, Instagram, and YouTube controllers to return popup callbacks instead of redirecting on session expiration or workspace not found. Enhance HandleInertiaRequests middleware to prevent deferring onboarding progress on social OAuth popup routes. Add tests to verify behavior for expired sessions and onboarding progress.
* Unify Instagram connect behind one card with a method picker.
Hide the Instagram-via-Facebook grid card and offer Instagram Login vs Facebook Pages from a single network entry, matching LinkedIn.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Move social popup onboarding assertions into connection tests.
Cover the deferred-prop popup regression on Facebook, Instagram, and YouTube select routes instead of a synthetic onboarding share check.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Stop suppressing onboarding defer on all social routes.
Override onboardingProgress only in popupCallback so picker pages stay deferred and the close page does not re-hit select after session clear.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Always open the Instagram method dialog on connect.
Drop connectMethods and the single-method OAuth shortcut; the picker always offers both Login and Facebook Pages.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Filter Instagram dialog options by enabled platforms.
Keep always opening the method picker, but only list OAuth entry points that are turned on.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Extract Instagram connect methods into a dedicated helper.
Keep connectableOptions focused on shaping grid options while the enabled OAuth list lives in instagramConnectMethods().
Co-authored-by: Cursor <cursoragent@cursor.com>
* Harden Meta Graph pagination and localize Instagram connect copy.
Fail closed on Graph request errors and pathological paging, keep Instagram connect going when profile detail lookups time out, and translate the Instagram method dialog strings.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-08 15:01:46 +00:00
|
|
|
public function selectChannel(Request $request): InertiaResponse
|
2026-01-15 17:24:39 +00:00
|
|
|
{
|
|
|
|
|
$oauthData = session('youtube_oauth');
|
|
|
|
|
$workspaceId = session('social_connect_workspace');
|
|
|
|
|
|
|
|
|
|
if (! $oauthData || ! $workspaceId) {
|
Fix Facebook Page connect pagination (#212) (#253)
* Fix Facebook and Instagram-via-Facebook Page connect pagination.
Follow Graph API paging.next on /me/accounts so authorized non-first Pages are found and multi-Page accounts get the picker instead of silently connecting the first result.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Paginate Meta accounts until paging.next is exhausted.
Drop the artificial 50-page cap and stop only when there is no next URL, or the same request URL repeats (broken pagination loop).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Redact tokens in Graph pagination logs and harden test coverage.
Cover happy-path and failure cases for Meta /me/accounts pagination, including mid-loop failures, invalid paging.next, and Instagram pages without a linked IG account.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fail closed on incomplete Meta accounts pagination.
If a later /me/accounts page fails after earlier pages succeeded, throw instead of returning a truncated list that could auto-connect the wrong Page. Also revert the IG detail timeout that could wipe the whole connect list.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify Graph pagination helpers and page fetchers.
Bake the first request query into the URL, drop requestKey, and let IncompleteGraphPaginationException bubble from the controllers without catch/rethrow noise.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Move incomplete pagination exception under Social\Meta.
Colocate it with GraphPaginator so the Meta scope is clear from the namespace instead of a generic Social exception name.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Rename pagination exception to IncompleteMetaGraphPaginationException.
Keep it under Exceptions/Social with Meta in the class name instead of moving it into Services.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Make GraphPaginator results explicit before mapping pages.
Assign the paginated accounts to a variable first so the Facebook and Instagram-via-Facebook fetchers read more clearly.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Build Meta Graph pagination URLs with Laravel Uri.
Replace manual http_build_query concatenation with Uri::of()->withQuery().
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use Laravel HTTP and Uri helpers in Meta Graph pagination.
Prefer response collect/json key access, filled(), and Uri path parsing over manual array and parse_url handling.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify graphVersion using Uri path and str().
Drop basename and native string casts; Uri::path() already yields the Graph API version segment.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Drop unnecessary str() around graph API config.
Uri: :of() already accepts the string returned by config().
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify GraphPaginator with Laravel helpers.
Consolidate failure handling via abort(), and use collect, when, throw_if, and Uri::value() for a shorter pagination loop.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Refactor social OAuth page/channel selection handling. Update selectPage and selectChannel methods in Facebook, Instagram, and YouTube controllers to return popup callbacks instead of redirecting on session expiration or workspace not found. Enhance HandleInertiaRequests middleware to prevent deferring onboarding progress on social OAuth popup routes. Add tests to verify behavior for expired sessions and onboarding progress.
* Unify Instagram connect behind one card with a method picker.
Hide the Instagram-via-Facebook grid card and offer Instagram Login vs Facebook Pages from a single network entry, matching LinkedIn.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Move social popup onboarding assertions into connection tests.
Cover the deferred-prop popup regression on Facebook, Instagram, and YouTube select routes instead of a synthetic onboarding share check.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Stop suppressing onboarding defer on all social routes.
Override onboardingProgress only in popupCallback so picker pages stay deferred and the close page does not re-hit select after session clear.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Always open the Instagram method dialog on connect.
Drop connectMethods and the single-method OAuth shortcut; the picker always offers both Login and Facebook Pages.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Filter Instagram dialog options by enabled platforms.
Keep always opening the method picker, but only list OAuth entry points that are turned on.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Extract Instagram connect methods into a dedicated helper.
Keep connectableOptions focused on shaping grid options while the enabled OAuth list lives in instagramConnectMethods().
Co-authored-by: Cursor <cursoragent@cursor.com>
* Harden Meta Graph pagination and localize Instagram connect copy.
Fail closed on Graph request errors and pathological paging, keep Instagram connect going when profile detail lookups time out, and translate the Instagram method dialog strings.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-08 15:01:46 +00:00
|
|
|
return $this->popupCallback(false, __('accounts.popup_callback.session_expired'), $this->platform->value);
|
2026-01-15 17:24:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$workspace = Workspace::find($workspaceId);
|
|
|
|
|
|
|
|
|
|
if (! $workspace) {
|
Fix Facebook Page connect pagination (#212) (#253)
* Fix Facebook and Instagram-via-Facebook Page connect pagination.
Follow Graph API paging.next on /me/accounts so authorized non-first Pages are found and multi-Page accounts get the picker instead of silently connecting the first result.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Paginate Meta accounts until paging.next is exhausted.
Drop the artificial 50-page cap and stop only when there is no next URL, or the same request URL repeats (broken pagination loop).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Redact tokens in Graph pagination logs and harden test coverage.
Cover happy-path and failure cases for Meta /me/accounts pagination, including mid-loop failures, invalid paging.next, and Instagram pages without a linked IG account.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fail closed on incomplete Meta accounts pagination.
If a later /me/accounts page fails after earlier pages succeeded, throw instead of returning a truncated list that could auto-connect the wrong Page. Also revert the IG detail timeout that could wipe the whole connect list.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify Graph pagination helpers and page fetchers.
Bake the first request query into the URL, drop requestKey, and let IncompleteGraphPaginationException bubble from the controllers without catch/rethrow noise.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Move incomplete pagination exception under Social\Meta.
Colocate it with GraphPaginator so the Meta scope is clear from the namespace instead of a generic Social exception name.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Rename pagination exception to IncompleteMetaGraphPaginationException.
Keep it under Exceptions/Social with Meta in the class name instead of moving it into Services.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Make GraphPaginator results explicit before mapping pages.
Assign the paginated accounts to a variable first so the Facebook and Instagram-via-Facebook fetchers read more clearly.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Build Meta Graph pagination URLs with Laravel Uri.
Replace manual http_build_query concatenation with Uri::of()->withQuery().
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use Laravel HTTP and Uri helpers in Meta Graph pagination.
Prefer response collect/json key access, filled(), and Uri path parsing over manual array and parse_url handling.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify graphVersion using Uri path and str().
Drop basename and native string casts; Uri::path() already yields the Graph API version segment.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Drop unnecessary str() around graph API config.
Uri: :of() already accepts the string returned by config().
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify GraphPaginator with Laravel helpers.
Consolidate failure handling via abort(), and use collect, when, throw_if, and Uri::value() for a shorter pagination loop.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Refactor social OAuth page/channel selection handling. Update selectPage and selectChannel methods in Facebook, Instagram, and YouTube controllers to return popup callbacks instead of redirecting on session expiration or workspace not found. Enhance HandleInertiaRequests middleware to prevent deferring onboarding progress on social OAuth popup routes. Add tests to verify behavior for expired sessions and onboarding progress.
* Unify Instagram connect behind one card with a method picker.
Hide the Instagram-via-Facebook grid card and offer Instagram Login vs Facebook Pages from a single network entry, matching LinkedIn.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Move social popup onboarding assertions into connection tests.
Cover the deferred-prop popup regression on Facebook, Instagram, and YouTube select routes instead of a synthetic onboarding share check.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Stop suppressing onboarding defer on all social routes.
Override onboardingProgress only in popupCallback so picker pages stay deferred and the close page does not re-hit select after session clear.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Always open the Instagram method dialog on connect.
Drop connectMethods and the single-method OAuth shortcut; the picker always offers both Login and Facebook Pages.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Filter Instagram dialog options by enabled platforms.
Keep always opening the method picker, but only list OAuth entry points that are turned on.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Extract Instagram connect methods into a dedicated helper.
Keep connectableOptions focused on shaping grid options while the enabled OAuth list lives in instagramConnectMethods().
Co-authored-by: Cursor <cursoragent@cursor.com>
* Harden Meta Graph pagination and localize Instagram connect copy.
Fail closed on Graph request errors and pathological paging, keep Instagram connect going when profile detail lookups time out, and translate the Instagram method dialog strings.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-08 15:01:46 +00:00
|
|
|
return $this->popupCallback(false, __('accounts.popup_callback.workspace_not_found'), $this->platform->value);
|
2026-01-15 17:24:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fetch YouTube channels
|
feat: social account toggle action, API, MCP + full test coverage
- Extract ToggleSocialAccount action from SocialController
- Add API endpoints: GET /social-accounts, PUT /social-accounts/{id}/toggle
- Add MCP tools: ListSocialAccountsTool, ToggleSocialAccountTool
- Fix all MCP tools: findOrFail → find + Response::error for graceful errors
- Fix MCP tools using $request->validated() without validate() call
- Fix return types to Response|ResponseFactory for error paths
- Add SocialAccountResource is_active/status fields (no tokens exposed)
- Add 43 MCP tests covering all 18 tools (CRUD, validation, cross-workspace)
- Add API response structure tests for posts, hashtags, labels, workspace
- Add API validation tests for post create/update, api-key expiry, label color
- Add API cross-workspace delete tests for hashtags and labels
- Add app validation tests for hashtag/label update, invite fields, password
- Add auth required tests for notifications, profile delete, api-keys index
- Add media reorder validation tests
2026-03-31 04:42:39 +00:00
|
|
|
$channels = $this->fetchChannels(data_get($oauthData, 'access_token'));
|
2026-01-15 17:24:39 +00:00
|
|
|
|
|
|
|
|
if (empty($channels)) {
|
2026-01-17 02:46:30 +00:00
|
|
|
$this->forgetSocialConnectSession();
|
|
|
|
|
session()->forget('youtube_oauth');
|
2026-01-15 17:24:39 +00:00
|
|
|
|
Fix Facebook Page connect pagination (#212) (#253)
* Fix Facebook and Instagram-via-Facebook Page connect pagination.
Follow Graph API paging.next on /me/accounts so authorized non-first Pages are found and multi-Page accounts get the picker instead of silently connecting the first result.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Paginate Meta accounts until paging.next is exhausted.
Drop the artificial 50-page cap and stop only when there is no next URL, or the same request URL repeats (broken pagination loop).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Redact tokens in Graph pagination logs and harden test coverage.
Cover happy-path and failure cases for Meta /me/accounts pagination, including mid-loop failures, invalid paging.next, and Instagram pages without a linked IG account.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fail closed on incomplete Meta accounts pagination.
If a later /me/accounts page fails after earlier pages succeeded, throw instead of returning a truncated list that could auto-connect the wrong Page. Also revert the IG detail timeout that could wipe the whole connect list.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify Graph pagination helpers and page fetchers.
Bake the first request query into the URL, drop requestKey, and let IncompleteGraphPaginationException bubble from the controllers without catch/rethrow noise.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Move incomplete pagination exception under Social\Meta.
Colocate it with GraphPaginator so the Meta scope is clear from the namespace instead of a generic Social exception name.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Rename pagination exception to IncompleteMetaGraphPaginationException.
Keep it under Exceptions/Social with Meta in the class name instead of moving it into Services.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Make GraphPaginator results explicit before mapping pages.
Assign the paginated accounts to a variable first so the Facebook and Instagram-via-Facebook fetchers read more clearly.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Build Meta Graph pagination URLs with Laravel Uri.
Replace manual http_build_query concatenation with Uri::of()->withQuery().
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use Laravel HTTP and Uri helpers in Meta Graph pagination.
Prefer response collect/json key access, filled(), and Uri path parsing over manual array and parse_url handling.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify graphVersion using Uri path and str().
Drop basename and native string casts; Uri::path() already yields the Graph API version segment.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Drop unnecessary str() around graph API config.
Uri: :of() already accepts the string returned by config().
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify GraphPaginator with Laravel helpers.
Consolidate failure handling via abort(), and use collect, when, throw_if, and Uri::value() for a shorter pagination loop.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Refactor social OAuth page/channel selection handling. Update selectPage and selectChannel methods in Facebook, Instagram, and YouTube controllers to return popup callbacks instead of redirecting on session expiration or workspace not found. Enhance HandleInertiaRequests middleware to prevent deferring onboarding progress on social OAuth popup routes. Add tests to verify behavior for expired sessions and onboarding progress.
* Unify Instagram connect behind one card with a method picker.
Hide the Instagram-via-Facebook grid card and offer Instagram Login vs Facebook Pages from a single network entry, matching LinkedIn.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Move social popup onboarding assertions into connection tests.
Cover the deferred-prop popup regression on Facebook, Instagram, and YouTube select routes instead of a synthetic onboarding share check.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Stop suppressing onboarding defer on all social routes.
Override onboardingProgress only in popupCallback so picker pages stay deferred and the close page does not re-hit select after session clear.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Always open the Instagram method dialog on connect.
Drop connectMethods and the single-method OAuth shortcut; the picker always offers both Login and Facebook Pages.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Filter Instagram dialog options by enabled platforms.
Keep always opening the method picker, but only list OAuth entry points that are turned on.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Extract Instagram connect methods into a dedicated helper.
Keep connectableOptions focused on shaping grid options while the enabled OAuth list lives in instagramConnectMethods().
Co-authored-by: Cursor <cursoragent@cursor.com>
* Harden Meta Graph pagination and localize Instagram connect copy.
Fail closed on Graph request errors and pathological paging, keep Instagram connect going when profile detail lookups time out, and translate the Instagram method dialog strings.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-08 15:01:46 +00:00
|
|
|
return $this->popupCallback(false, __('accounts.popup_callback.no_youtube_channels'), $this->platform->value);
|
2026-01-15 17:24:39 +00:00
|
|
|
}
|
|
|
|
|
|
Fix Facebook Page connect pagination (#212) (#253)
* Fix Facebook and Instagram-via-Facebook Page connect pagination.
Follow Graph API paging.next on /me/accounts so authorized non-first Pages are found and multi-Page accounts get the picker instead of silently connecting the first result.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Paginate Meta accounts until paging.next is exhausted.
Drop the artificial 50-page cap and stop only when there is no next URL, or the same request URL repeats (broken pagination loop).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Redact tokens in Graph pagination logs and harden test coverage.
Cover happy-path and failure cases for Meta /me/accounts pagination, including mid-loop failures, invalid paging.next, and Instagram pages without a linked IG account.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fail closed on incomplete Meta accounts pagination.
If a later /me/accounts page fails after earlier pages succeeded, throw instead of returning a truncated list that could auto-connect the wrong Page. Also revert the IG detail timeout that could wipe the whole connect list.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify Graph pagination helpers and page fetchers.
Bake the first request query into the URL, drop requestKey, and let IncompleteGraphPaginationException bubble from the controllers without catch/rethrow noise.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Move incomplete pagination exception under Social\Meta.
Colocate it with GraphPaginator so the Meta scope is clear from the namespace instead of a generic Social exception name.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Rename pagination exception to IncompleteMetaGraphPaginationException.
Keep it under Exceptions/Social with Meta in the class name instead of moving it into Services.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Make GraphPaginator results explicit before mapping pages.
Assign the paginated accounts to a variable first so the Facebook and Instagram-via-Facebook fetchers read more clearly.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Build Meta Graph pagination URLs with Laravel Uri.
Replace manual http_build_query concatenation with Uri::of()->withQuery().
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use Laravel HTTP and Uri helpers in Meta Graph pagination.
Prefer response collect/json key access, filled(), and Uri path parsing over manual array and parse_url handling.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify graphVersion using Uri path and str().
Drop basename and native string casts; Uri::path() already yields the Graph API version segment.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Drop unnecessary str() around graph API config.
Uri: :of() already accepts the string returned by config().
Co-authored-by: Cursor <cursoragent@cursor.com>
* Simplify GraphPaginator with Laravel helpers.
Consolidate failure handling via abort(), and use collect, when, throw_if, and Uri::value() for a shorter pagination loop.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Refactor social OAuth page/channel selection handling. Update selectPage and selectChannel methods in Facebook, Instagram, and YouTube controllers to return popup callbacks instead of redirecting on session expiration or workspace not found. Enhance HandleInertiaRequests middleware to prevent deferring onboarding progress on social OAuth popup routes. Add tests to verify behavior for expired sessions and onboarding progress.
* Unify Instagram connect behind one card with a method picker.
Hide the Instagram-via-Facebook grid card and offer Instagram Login vs Facebook Pages from a single network entry, matching LinkedIn.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Move social popup onboarding assertions into connection tests.
Cover the deferred-prop popup regression on Facebook, Instagram, and YouTube select routes instead of a synthetic onboarding share check.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Stop suppressing onboarding defer on all social routes.
Override onboardingProgress only in popupCallback so picker pages stay deferred and the close page does not re-hit select after session clear.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Always open the Instagram method dialog on connect.
Drop connectMethods and the single-method OAuth shortcut; the picker always offers both Login and Facebook Pages.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Filter Instagram dialog options by enabled platforms.
Keep always opening the method picker, but only list OAuth entry points that are turned on.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Extract Instagram connect methods into a dedicated helper.
Keep connectableOptions focused on shaping grid options while the enabled OAuth list lives in instagramConnectMethods().
Co-authored-by: Cursor <cursoragent@cursor.com>
* Harden Meta Graph pagination and localize Instagram connect copy.
Fail closed on Graph request errors and pathological paging, keep Instagram connect going when profile detail lookups time out, and translate the Instagram method dialog strings.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-08 15:01:46 +00:00
|
|
|
return Inertia::render('accounts/YouTubeChannelSelect', [
|
2026-01-15 17:24:39 +00:00
|
|
|
'workspace' => $workspace,
|
|
|
|
|
'channels' => $channels,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-25 16:54:16 +00:00
|
|
|
public function select(Request $request): InertiaResponse
|
2026-01-15 17:24:39 +00:00
|
|
|
{
|
|
|
|
|
$request->validate([
|
|
|
|
|
'channel_id' => 'required|string',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$oauthData = session('youtube_oauth');
|
|
|
|
|
$workspaceId = session('social_connect_workspace');
|
|
|
|
|
|
|
|
|
|
if (! $oauthData || ! $workspaceId) {
|
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 17:03:52 +00:00
|
|
|
return $this->popupCallback(false, __('accounts.popup_callback.session_expired'), $this->platform->value);
|
2026-01-15 17:24:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$workspace = Workspace::find($workspaceId);
|
|
|
|
|
|
|
|
|
|
if (! $workspace || ! $request->user()->can('manageAccounts', $workspace)) {
|
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 17:03:52 +00:00
|
|
|
return $this->popupCallback(false, __('accounts.popup_callback.workspace_not_found'), $this->platform->value);
|
2026-01-15 17:24:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
feat: social account toggle action, API, MCP + full test coverage
- Extract ToggleSocialAccount action from SocialController
- Add API endpoints: GET /social-accounts, PUT /social-accounts/{id}/toggle
- Add MCP tools: ListSocialAccountsTool, ToggleSocialAccountTool
- Fix all MCP tools: findOrFail → find + Response::error for graceful errors
- Fix MCP tools using $request->validated() without validate() call
- Fix return types to Response|ResponseFactory for error paths
- Add SocialAccountResource is_active/status fields (no tokens exposed)
- Add 43 MCP tests covering all 18 tools (CRUD, validation, cross-workspace)
- Add API response structure tests for posts, hashtags, labels, workspace
- Add API validation tests for post create/update, api-key expiry, label color
- Add API cross-workspace delete tests for hashtags and labels
- Add app validation tests for hashtag/label update, invite fields, password
- Add auth required tests for notifications, profile delete, api-keys index
- Add media reorder validation tests
2026-03-31 04:42:39 +00:00
|
|
|
$channels = $this->fetchChannels(data_get($oauthData, 'access_token'));
|
2026-01-15 17:24:39 +00:00
|
|
|
$selectedChannel = collect($channels)->firstWhere('id', $request->channel_id);
|
|
|
|
|
|
|
|
|
|
if (! $selectedChannel) {
|
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 17:03:52 +00:00
|
|
|
return $this->popupCallback(false, __('accounts.popup_callback.channel_not_found'), $this->platform->value);
|
2026-01-15 17:24:39 +00:00
|
|
|
}
|
|
|
|
|
|
feat: social account toggle action, API, MCP + full test coverage
- Extract ToggleSocialAccount action from SocialController
- Add API endpoints: GET /social-accounts, PUT /social-accounts/{id}/toggle
- Add MCP tools: ListSocialAccountsTool, ToggleSocialAccountTool
- Fix all MCP tools: findOrFail → find + Response::error for graceful errors
- Fix MCP tools using $request->validated() without validate() call
- Fix return types to Response|ResponseFactory for error paths
- Add SocialAccountResource is_active/status fields (no tokens exposed)
- Add 43 MCP tests covering all 18 tools (CRUD, validation, cross-workspace)
- Add API response structure tests for posts, hashtags, labels, workspace
- Add API validation tests for post create/update, api-key expiry, label color
- Add API cross-workspace delete tests for hashtags and labels
- Add app validation tests for hashtag/label update, invite fields, password
- Add auth required tests for notifications, profile delete, api-keys index
- Add media reorder validation tests
2026-03-31 04:42:39 +00:00
|
|
|
$avatarPath = uploadFromUrl(data_get($selectedChannel, 'thumbnail'));
|
|
|
|
|
$reconnectId = data_get($oauthData, 'reconnect_id', null);
|
2026-01-17 02:46:30 +00:00
|
|
|
|
|
|
|
|
if ($reconnectId) {
|
|
|
|
|
// Reconnect existing account
|
|
|
|
|
$existingAccount = $workspace->socialAccounts()->find($reconnectId);
|
|
|
|
|
|
|
|
|
|
if ($existingAccount) {
|
|
|
|
|
$existingAccount->update([
|
feat: social account toggle action, API, MCP + full test coverage
- Extract ToggleSocialAccount action from SocialController
- Add API endpoints: GET /social-accounts, PUT /social-accounts/{id}/toggle
- Add MCP tools: ListSocialAccountsTool, ToggleSocialAccountTool
- Fix all MCP tools: findOrFail → find + Response::error for graceful errors
- Fix MCP tools using $request->validated() without validate() call
- Fix return types to Response|ResponseFactory for error paths
- Add SocialAccountResource is_active/status fields (no tokens exposed)
- Add 43 MCP tests covering all 18 tools (CRUD, validation, cross-workspace)
- Add API response structure tests for posts, hashtags, labels, workspace
- Add API validation tests for post create/update, api-key expiry, label color
- Add API cross-workspace delete tests for hashtags and labels
- Add app validation tests for hashtag/label update, invite fields, password
- Add auth required tests for notifications, profile delete, api-keys index
- Add media reorder validation tests
2026-03-31 04:42:39 +00:00
|
|
|
'platform_user_id' => data_get($selectedChannel, 'id'),
|
|
|
|
|
'username' => ltrim(data_get($selectedChannel, 'custom_url', data_get($selectedChannel, 'id')), '@'),
|
|
|
|
|
'display_name' => data_get($selectedChannel, 'title'),
|
2026-01-17 02:46:30 +00:00
|
|
|
'avatar_url' => $avatarPath,
|
feat: social account toggle action, API, MCP + full test coverage
- Extract ToggleSocialAccount action from SocialController
- Add API endpoints: GET /social-accounts, PUT /social-accounts/{id}/toggle
- Add MCP tools: ListSocialAccountsTool, ToggleSocialAccountTool
- Fix all MCP tools: findOrFail → find + Response::error for graceful errors
- Fix MCP tools using $request->validated() without validate() call
- Fix return types to Response|ResponseFactory for error paths
- Add SocialAccountResource is_active/status fields (no tokens exposed)
- Add 43 MCP tests covering all 18 tools (CRUD, validation, cross-workspace)
- Add API response structure tests for posts, hashtags, labels, workspace
- Add API validation tests for post create/update, api-key expiry, label color
- Add API cross-workspace delete tests for hashtags and labels
- Add app validation tests for hashtag/label update, invite fields, password
- Add auth required tests for notifications, profile delete, api-keys index
- Add media reorder validation tests
2026-03-31 04:42:39 +00:00
|
|
|
'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,
|
2026-01-17 02:46:30 +00:00
|
|
|
'scopes' => $this->scopes,
|
|
|
|
|
'meta' => [
|
feat: social account toggle action, API, MCP + full test coverage
- Extract ToggleSocialAccount action from SocialController
- Add API endpoints: GET /social-accounts, PUT /social-accounts/{id}/toggle
- Add MCP tools: ListSocialAccountsTool, ToggleSocialAccountTool
- Fix all MCP tools: findOrFail → find + Response::error for graceful errors
- Fix MCP tools using $request->validated() without validate() call
- Fix return types to Response|ResponseFactory for error paths
- Add SocialAccountResource is_active/status fields (no tokens exposed)
- Add 43 MCP tests covering all 18 tools (CRUD, validation, cross-workspace)
- Add API response structure tests for posts, hashtags, labels, workspace
- Add API validation tests for post create/update, api-key expiry, label color
- Add API cross-workspace delete tests for hashtags and labels
- Add app validation tests for hashtag/label update, invite fields, password
- Add auth required tests for notifications, profile delete, api-keys index
- Add media reorder validation tests
2026-03-31 04:42:39 +00:00
|
|
|
'channel_id' => data_get($selectedChannel, 'id'),
|
|
|
|
|
'google_user_id' => data_get($oauthData, 'user_id'),
|
2026-01-17 02:46:30 +00:00
|
|
|
],
|
|
|
|
|
]);
|
|
|
|
|
$existingAccount->markAsConnected();
|
|
|
|
|
|
|
|
|
|
session()->forget(['youtube_oauth', 'social_reconnect_id']);
|
|
|
|
|
|
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 17:03:52 +00:00
|
|
|
return $this->popupCallback(true, __('accounts.popup_callback.reconnected'), $this->platform->value);
|
2026-01-17 02:46:30 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-01-15 17:24:39 +00:00
|
|
|
|
2026-04-15 12:46:18 +00:00
|
|
|
$workspace->socialAccounts()->updateOrCreate(
|
|
|
|
|
[
|
|
|
|
|
'platform' => $this->platform->value,
|
|
|
|
|
'platform_user_id' => data_get($selectedChannel, 'id'),
|
2026-01-15 17:24:39 +00:00
|
|
|
],
|
2026-04-15 12:46:18 +00:00
|
|
|
[
|
|
|
|
|
'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'),
|
|
|
|
|
],
|
|
|
|
|
],
|
|
|
|
|
);
|
2026-01-15 17:24:39 +00:00
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
session()->forget(['youtube_oauth', 'social_reconnect_id']);
|
2026-01-15 17:24:39 +00:00
|
|
|
|
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 17:03:52 +00:00
|
|
|
return $this->popupCallback(true, __('accounts.popup_callback.connected'), $this->platform->value);
|
2026-06-22 00:28:47 +00:00
|
|
|
} catch (NetworkAlreadyConnectedException) {
|
|
|
|
|
return $this->popupCallback(false, __('accounts.popup_callback.network_taken'), $this->platform->value);
|
2026-01-15 17:24:39 +00:00
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
Log::error('YouTube channel selection error', [
|
|
|
|
|
'error' => $e->getMessage(),
|
|
|
|
|
]);
|
|
|
|
|
|
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 17:03:52 +00:00
|
|
|
return $this->popupCallback(false, __('accounts.popup_callback.error_connecting_channel'), $this->platform->value);
|
2026-01-15 17:24:39 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
private function redirectToGoogle(): Response
|
2026-01-15 17:24:39 +00:00
|
|
|
{
|
2026-03-29 22:24:28 +00:00
|
|
|
return Inertia::location(
|
2026-01-15 17:24:39 +00:00
|
|
|
Socialite::driver($this->driver)
|
|
|
|
|
->scopes($this->scopes)
|
|
|
|
|
->with([
|
|
|
|
|
'access_type' => 'offline',
|
|
|
|
|
'prompt' => 'consent',
|
2026-01-26 16:41:15 +00:00
|
|
|
'include_granted_scopes' => 'true',
|
2026-01-15 17:24:39 +00:00
|
|
|
])
|
|
|
|
|
->redirect()
|
|
|
|
|
->getTargetUrl()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function fetchChannels(string $accessToken): array
|
|
|
|
|
{
|
|
|
|
|
try {
|
2026-03-29 22:24:28 +00:00
|
|
|
$response = Http::withToken($accessToken)
|
2026-05-02 16:49:20 +00:00
|
|
|
->get(config('trypost.platforms.youtube.data_api').'/channels', [
|
2026-01-15 17:24:39 +00:00
|
|
|
'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();
|
|
|
|
|
|
2026-03-31 03:40:18 +00:00
|
|
|
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),
|
2026-01-15 17:24:39 +00:00
|
|
|
])->toArray();
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
Log::error('YouTube channels fetch error', [
|
|
|
|
|
'error' => $e->getMessage(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|