diff --git a/.env.example b/.env.example index 1e93f9e..803837b 100644 --- a/.env.example +++ b/.env.example @@ -26,6 +26,10 @@ BILLING_API_KEY_WOO= IDENTITY_API_URL=https://ladill.com/api IDENTITY_API_KEY_WOO= +# Suite demo accounts (sales walkthroughs) — reset Woo Manager data on each SSO login +DEMO_ACCOUNTS_ENABLED=false +DEMO_RESET_ON_LOGIN=true + # SMTP — production deploy copies transport vars from /var/www/ladill.com/shared/.env MAIL_MAILER=log MAIL_SCHEME=null diff --git a/app/Http/Controllers/Auth/SsoLoginController.php b/app/Http/Controllers/Auth/SsoLoginController.php index 13a3d8b..b079cff 100644 --- a/app/Http/Controllers/Auth/SsoLoginController.php +++ b/app/Http/Controllers/Auth/SsoLoginController.php @@ -132,6 +132,7 @@ class SsoLoginController extends Controller $request->session()->forget('sso.attempts'); app(\App\Services\Woo\TeamMemberProvisioner::class)->sync($user); + app(\App\Services\Woo\DemoLoginReseeder::class)->maybeResetAfterLogin($user); return $this->finishCallback($request, $intended, null, $popup); } diff --git a/app/Services/Woo/DemoLoginReseeder.php b/app/Services/Woo/DemoLoginReseeder.php new file mode 100644 index 0000000..2f1b66e --- /dev/null +++ b/app/Services/Woo/DemoLoginReseeder.php @@ -0,0 +1,86 @@ +planForEmail($user->email); + if ($plan === null) { + return; + } + + $seedUser = $this->resolveSeedUser($user); + if (! $seedUser) { + return; + } + + $lockKey = 'demo-reseed:woo:'.$seedUser->public_id; + if (! Cache::add($lockKey, 1, 120)) { + return; + } + + $identity = (string) ($seedUser->email ?: $seedUser->public_id); + + dispatch(function () use ($identity, $plan, $lockKey) { + try { + Artisan::call('demo:seed', [ + 'identity' => $identity, + '--plan' => $plan, + '--reset' => true, + ]); + } catch (\Throwable $e) { + Log::warning('demo_woo_login_reseed_failed', [ + 'identity' => $identity, + 'plan' => $plan, + 'error' => $e->getMessage(), + ]); + } finally { + Cache::forget($lockKey); + } + })->afterResponse(); + } + + private function planForEmail(?string $email): ?string + { + $email = strtolower(trim((string) $email)); + if ($email === '') { + return null; + } + + $accounts = (array) config('demo_accounts.accounts', []); + if (isset($accounts[$email])) { + return $accounts[$email]; + } + + $members = (array) config('demo_accounts.member_emails', []); + $ownerEmail = $members[$email] ?? null; + + return $ownerEmail ? ($accounts[$ownerEmail] ?? null) : null; + } + + private function resolveSeedUser(User $user): ?User + { + $email = strtolower((string) $user->email); + $ownerEmail = ((array) config('demo_accounts.member_emails', []))[$email] ?? null; + if (! $ownerEmail) { + return $user; + } + + return User::query()->where('email', $ownerEmail)->first() ?? $user; + } +} diff --git a/config/demo_accounts.php b/config/demo_accounts.php new file mode 100644 index 0000000..5adaff8 --- /dev/null +++ b/config/demo_accounts.php @@ -0,0 +1,18 @@ + (bool) env('DEMO_ACCOUNTS_ENABLED', false), + 'reset_on_login' => (bool) env('DEMO_RESET_ON_LOGIN', true), + + 'accounts' => [ + 'demo-free@ladill.com' => 'free', + 'demo-pro@ladill.com' => 'pro', + 'demo-enterprise@ladill.com' => 'enterprise', + ], + + // Org members share the enterprise owner tenant. + 'member_emails' => [ + 'demo-enterprise-member-1@ladill.com' => 'demo-enterprise@ladill.com', + 'demo-enterprise-member-2@ladill.com' => 'demo-enterprise@ladill.com', + ], +]; diff --git a/tests/Feature/DemoLoginReseederTest.php b/tests/Feature/DemoLoginReseederTest.php new file mode 100644 index 0000000..51e35a1 --- /dev/null +++ b/tests/Feature/DemoLoginReseederTest.php @@ -0,0 +1,77 @@ + true, + 'demo_accounts.reset_on_login' => true, + ]); + Cache::flush(); + } + + public function test_it_reseeds_demo_free_user_after_login(): void + { + $user = User::factory()->create([ + 'public_id' => 'demo-free-pid', + 'name' => 'Ladill Demo (Free)', + 'email' => 'demo-free@ladill.com', + ]); + + Artisan::shouldReceive('call') + ->once() + ->with('demo:seed', [ + 'identity' => $user->email, + '--plan' => 'free', + '--reset' => true, + ]) + ->andReturn(0); + + app(DemoLoginReseeder::class)->maybeResetAfterLogin($user); + + $this->app->terminate(); + } + + public function test_it_skips_non_demo_users(): void + { + $user = User::factory()->create([ + 'public_id' => 'regular-pid', + 'name' => 'Regular', + 'email' => 'regular@example.com', + ]); + + Artisan::shouldReceive('call')->never(); + + app(DemoLoginReseeder::class)->maybeResetAfterLogin($user); + $this->app->terminate(); + } + + public function test_it_skips_when_disabled(): void + { + config(['demo_accounts.enabled' => false]); + + $user = User::factory()->create([ + 'public_id' => 'demo-free-pid', + 'name' => 'Ladill Demo (Free)', + 'email' => 'demo-free@ladill.com', + ]); + + Artisan::shouldReceive('call')->never(); + + app(DemoLoginReseeder::class)->maybeResetAfterLogin($user); + $this->app->terminate(); + } +}