trypost/app/Actions/Workspace/CreateWorkspace.php
Paulo Castellano 17a091975d fix: merge-readiness — close two billing/network bugs, harden tests
Bugs (both with regression tests):
- OnboardingController::store now guards already-subscribed accounts (mirrors
  index), preventing a second Stripe Checkout / double subscription if a
  subscribed user re-POSTs /onboarding.
- SocialAccountObserver: drop the `platform_user_id != …` clause from the
  creating-time one-per-network check. On create there is no "self" to exclude,
  so it only weakened the rule — the same account connected via two network
  variants (e.g. Instagram standalone + via Facebook, same id) could slip a
  second account into the network. Now any account in the network blocks.

Robustness:
- CreateWorkspace wraps create + member attach + switchWorkspace in a
  transaction (cache-forget / quantity-sync run after), so a partial failure
  can't leave an orphan workspace that inflates the Stripe seat count — covers
  both the signup and the add-workspace paths.

Test honesty & coverage:
- Scope the ten "connect multiple <platform> accounts" tests to self-hosted
  mode (config + name); they only passed because the test env defaults
  SELF_HOSTED=true, and in cloud the one-per-network rule blocks them.
- network_taken popup now has controller-level tests on all six OAuth
  controllers (added Threads, YouTube, LinkedInPage, and a new
  InstagramFacebook test file; LinkedInPage/InstagramFacebook also exercise
  variant collapse).
- Wiring tests that creating/deleting a workspace actually calls
  syncWorkspaceQuantity (guards per-seat billing against silent breakage).
- Strengthen TrialLengthTest to assert the configured length reaches
  trial_ends_at; add a same-id network-variant block test.
2026-06-22 09:26:31 -03:00

50 lines
1.7 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Actions\Workspace;
use App\Enums\UserWorkspace\Role;
use App\Models\User;
use App\Models\Workspace;
use Illuminate\Support\Facades\DB;
class CreateWorkspace
{
/**
* @param array<string, mixed> $data
*/
public static function execute(User $user, array $data): Workspace
{
$attributes = array_filter([
'name' => data_get($data, 'name'),
'brand_website' => data_get($data, 'brand_website'),
'brand_description' => data_get($data, 'brand_description'),
'brand_voice_traits' => data_get($data, 'brand_voice_traits'),
'brand_color' => data_get($data, 'brand_color'),
'background_color' => data_get($data, 'background_color'),
'text_color' => data_get($data, 'text_color'),
'content_language' => data_get($data, 'content_language'),
], static fn ($value): bool => $value !== null);
$workspace = DB::transaction(function () use ($user, $attributes): Workspace {
$workspace = Workspace::create([
...$attributes,
'account_id' => $user->account_id,
'user_id' => $user->id,
]);
// Creator becomes Admin of the workspace they made. The Account Owner
// is resolved separately (via account.owner_id) and outranks this role.
$workspace->members()->attach($user->id, ['role' => Role::Admin->value]);
$user->switchWorkspace($workspace);
return $workspace;
});
$user->account?->forgetPlanFeatureCache();
$user->account?->syncWorkspaceQuantity();
return $workspace;
}
}