refactor: address third-pass review nits

- CreateUser: provision the default workspace OUTSIDE the signup DB transaction
  so CreateWorkspace's cache-forget / Stripe-quantity sync never runs inside a
  transaction (consistent with WorkspaceController::store); user+account stay
  atomic and a failed workspace create degrades to /workspaces/create.
- BillingController::swapToYearly: explicit null guard on the resolved
  subscription before dereferencing stripe_price.
- UsageController: reuse the already-loaded workspaces collection for the count
  instead of issuing a second query.
- Add a GitHub OAuth-callback test covering the default-workspace auto-create
  (mirrors the Google one).
This commit is contained in:
Paulo Castellano 2026-06-22 08:39:25 -03:00
parent b7374c1adb
commit caad3d3e53
4 changed files with 37 additions and 7 deletions

View file

@ -21,8 +21,9 @@ class CreateUser
*/
public static function execute(array $data, array $utmParameters = []): User
{
$user = DB::transaction(function () use ($data, $utmParameters): User {
$isInviteRegistration = data_get($data, 'is_invite', false);
$isInviteRegistration = (bool) data_get($data, 'is_invite', false);
$user = DB::transaction(function () use ($data, $utmParameters, $isInviteRegistration): User {
$requiresCardForTrial = (bool) config('trypost.billing.require_card_for_trial', true);
$accountAttributes = [
'name' => data_get($data, 'name')."'s Account",
@ -49,13 +50,13 @@ public static function execute(array $data, array $utmParameters = []): User
$account->update(['owner_id' => $user->id]);
if (! $isInviteRegistration) {
CreateWorkspace::execute($user, ['name' => data_get($data, 'name')."'s Workspace"]);
}
return $user;
});
if (! $isInviteRegistration) {
CreateWorkspace::execute($user, ['name' => data_get($data, 'name')."'s Workspace"]);
}
if (PostHogService::isEnabled()) {
SyncUser::dispatch((string) $user->id);
}

View file

@ -130,6 +130,8 @@ public function swapToYearly(Request $request): RedirectResponse
$subscription = $account->subscription(Account::SUBSCRIPTION_NAME);
abort_if($subscription === null, SymfonyResponse::HTTP_UNPROCESSABLE_ENTITY, 'No active subscription');
if ($subscription->stripe_price === $yearlyPriceId) {
return redirect()->route('app.billing.index');
}

View file

@ -35,7 +35,7 @@ public function index(Request $request): Response|RedirectResponse
return Inertia::render('settings/account/Usage', [
'usage' => [
'workspaceCount' => $account->workspaces()->count(),
'workspaceCount' => $account->workspaces->count(),
'socialAccountCount' => $totalSocialAccounts,
'memberCount' => $totalMembers,
'creditsUsed' => BillingCycle::for($account)->usedCredits(),

View file

@ -72,6 +72,33 @@
$this->assertAuthenticatedAs($user);
});
test('github callback creates new user with a default workspace', function () {
$socialiteUser = new SocialiteUser;
$socialiteUser->map([
'id' => '987',
'name' => 'New Dev',
'email' => 'newdev@example.com',
]);
Socialite::shouldReceive('driver')
->with('github')
->andReturn($driver = Mockery::mock());
$driver->shouldReceive('user')->andReturn($socialiteUser);
$response = $this->get(route('auth.github.callback'));
$response->assertRedirect(route('register.success'));
$user = User::where('email', 'newdev@example.com')->first();
expect($user)->not->toBeNull();
expect($user->github_id)->toBe('987');
expect($user->name)->toBe('New Dev');
expect($user->workspaces()->count())->toBe(1);
expect($user->workspaces()->first()->name)->toBe("New Dev's Workspace");
expect($user->current_workspace_id)->toBe($user->workspaces()->first()->id);
$this->assertAuthenticatedAs($user);
});
test('google callback marks unverified existing user as verified', function () {
$user = User::factory()->create([
'email' => 'unverified@example.com',