From b0590842a4dfb897aae27b4297b9974f24190b41 Mon Sep 17 00:00:00 2001 From: isaacclad Date: Fri, 17 Jul 2026 11:21:10 +0000 Subject: [PATCH] Reseed demo tenants after each SSO login. Demo Free/Pro/Enterprise accounts wipe and reload sample data on authorization-code SSO callbacks so every sales walkthrough starts clean. Co-authored-by: Cursor --- .env.example | 4 + .../Controllers/Auth/SsoLoginController.php | 1 + app/Services/Meet/DemoLoginReseeder.php | 86 +++++++++++++++++++ config/demo_accounts.php | 18 ++++ tests/Feature/DemoLoginReseederTest.php | 79 +++++++++++++++++ 5 files changed, 188 insertions(+) create mode 100644 app/Services/Meet/DemoLoginReseeder.php create mode 100644 config/demo_accounts.php create mode 100644 tests/Feature/DemoLoginReseederTest.php diff --git a/.env.example b/.env.example index 89766b0..25d42d1 100644 --- a/.env.example +++ b/.env.example @@ -65,6 +65,10 @@ MEET_AI_MODEL=gpt-4o-mini MEET_AI_PLATFORM_URL= MEET_AI_PLATFORM_KEY= +# Suite demo accounts (sales walkthroughs) — reset Meet data on each SSO login +DEMO_ACCOUNTS_ENABLED=false +DEMO_RESET_ON_LOGIN=true + # --- Inbound service API keys (sibling Ladill apps calling Meet) --- MEET_API_KEY_CARE= MEET_API_KEY_CRM= diff --git a/app/Http/Controllers/Auth/SsoLoginController.php b/app/Http/Controllers/Auth/SsoLoginController.php index 1617072..00701be 100644 --- a/app/Http/Controllers/Auth/SsoLoginController.php +++ b/app/Http/Controllers/Auth/SsoLoginController.php @@ -121,6 +121,7 @@ class SsoLoginController extends Controller $request->session()->forget('sso.attempts'); app(TeamMemberProvisioner::class)->sync($user); + app(\App\Services\Meet\DemoLoginReseeder::class)->maybeResetAfterLogin($user); return $this->finishCallback($request, $intended, null, app(IdentityTeamClient::class)); } diff --git a/app/Services/Meet/DemoLoginReseeder.php b/app/Services/Meet/DemoLoginReseeder.php new file mode 100644 index 0000000..291278f --- /dev/null +++ b/app/Services/Meet/DemoLoginReseeder.php @@ -0,0 +1,86 @@ +planForEmail($user->email); + if ($plan === null) { + return; + } + + $seedUser = $this->resolveSeedUser($user); + if (! $seedUser) { + return; + } + + $lockKey = 'demo-reseed:meet:'.$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_meet_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..c4c4e57 --- /dev/null +++ b/tests/Feature/DemoLoginReseederTest.php @@ -0,0 +1,79 @@ +withoutMiddleware(EnsurePlatformSession::class); + config([ + 'demo_accounts.enabled' => true, + 'demo_accounts.reset_on_login' => true, + ]); + Cache::flush(); + } + + public function test_it_reseeds_demo_free_user_after_login(): void + { + $user = User::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::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::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(); + } +}