|
26 | 26 | use Fleetbase\Twilio\Support\Laravel\Facade as Twilio; |
27 | 27 | use Illuminate\Http\Request; |
28 | 28 | use Illuminate\Support\Carbon; |
| 29 | +use Illuminate\Support\Facades\Cache; |
| 30 | +use Illuminate\Support\Facades\DB; |
29 | 31 | use Illuminate\Support\Facades\Mail; |
30 | 32 | use Illuminate\Support\Facades\Redis; |
31 | 33 | use Illuminate\Support\Str; |
@@ -101,31 +103,133 @@ public function login(LoginRequest $request) |
101 | 103 | */ |
102 | 104 | public function session(Request $request) |
103 | 105 | { |
104 | | - $user = $request->user(); |
105 | | - if (!$user) { |
106 | | - return response()->error('Session has expired.', 401, ['restore' => false]); |
107 | | - } |
| 106 | + $token = $request->bearerToken(); |
| 107 | + $cacheKey = "session_validation_{$token}"; |
| 108 | + |
| 109 | + // Cache session validation for 5 minutes |
| 110 | + $session = Cache::remember($cacheKey, now()->addMinutes(5), function () use ($request) { |
| 111 | + $user = $request->user(); |
108 | 112 |
|
109 | | - $session = ['token' => $request->bearerToken(), 'user' => $user->uuid, 'verified' => $user->isVerified(), 'type' => $user->getType()]; |
110 | | - if (session()->has('impersonator')) { |
111 | | - $session['impersonator'] = session()->get('impersonator'); |
| 113 | + if (!$user) { |
| 114 | + return null; |
| 115 | + } |
| 116 | + |
| 117 | + $sessionData = [ |
| 118 | + 'token' => $request->bearerToken(), |
| 119 | + 'user' => $user->uuid, |
| 120 | + 'verified' => $user->isVerified(), |
| 121 | + 'type' => $user->getType(), |
| 122 | + ]; |
| 123 | + |
| 124 | + if (session()->has('impersonator')) { |
| 125 | + $sessionData['impersonator'] = session()->get('impersonator'); |
| 126 | + } |
| 127 | + |
| 128 | + return $sessionData; |
| 129 | + }); |
| 130 | + |
| 131 | + if (!$session) { |
| 132 | + return response()->error('Session has expired.', 401, ['restore' => false]); |
112 | 133 | } |
113 | 134 |
|
114 | | - return response()->json($session); |
| 135 | + return response()->json($session) |
| 136 | + ->header('Cache-Control', 'private, max-age=300'); // 5 minutes |
115 | 137 | } |
116 | 138 |
|
117 | 139 | /** |
118 | 140 | * Logs out the currently authenticated user. |
119 | 141 | * |
120 | 142 | * @return \Illuminate\Http\Response |
121 | 143 | */ |
122 | | - public function logout() |
| 144 | + public function logout(Request $request) |
123 | 145 | { |
| 146 | + $token = $request->bearerToken(); |
| 147 | + Cache::forget("session_validation_{$token}"); |
| 148 | + |
124 | 149 | Auth::logout(); |
125 | 150 |
|
126 | 151 | return response()->json(['Goodbye']); |
127 | 152 | } |
128 | 153 |
|
| 154 | + /** |
| 155 | + * Bootstrap endpoint - combines session, organizations, and installer status. |
| 156 | + * |
| 157 | + * @param Request $request |
| 158 | + * |
| 159 | + * @return \Illuminate\Http\Response |
| 160 | + */ |
| 161 | + public function bootstrap(Request $request) |
| 162 | + { |
| 163 | + $user = $request->user(); |
| 164 | + $token = $request->bearerToken(); |
| 165 | + $cacheKey = "auth_bootstrap_{$user->uuid}_{$token}"; |
| 166 | + |
| 167 | + // Cache for 5 minutes |
| 168 | + $bootstrap = Cache::remember($cacheKey, now()->addMinutes(5), function () use ($request, $user) { |
| 169 | + // Get session data |
| 170 | + $session = [ |
| 171 | + 'token' => $request->bearerToken(), |
| 172 | + 'user' => $user->uuid, |
| 173 | + 'verified' => $user->isVerified(), |
| 174 | + 'type' => $user->getType(), |
| 175 | + ]; |
| 176 | + |
| 177 | + if (session()->has('impersonator')) { |
| 178 | + $session['impersonator'] = session()->get('impersonator'); |
| 179 | + } |
| 180 | + |
| 181 | + // Get organizations (optimized query) |
| 182 | + $organizations = Company::select([ |
| 183 | + 'companies.uuid', |
| 184 | + 'companies.name', |
| 185 | + 'companies.owner_uuid', |
| 186 | + ]) |
| 187 | + ->join('company_users', 'companies.uuid', '=', 'company_users.company_uuid') |
| 188 | + ->where('company_users.user_uuid', $user->uuid) |
| 189 | + ->whereNull('company_users.deleted_at') |
| 190 | + ->whereNotNull('companies.owner_uuid') |
| 191 | + ->with(['owner:uuid,company_uuid,name,email']) |
| 192 | + ->distinct() |
| 193 | + ->get(); |
| 194 | + |
| 195 | + // Get installer status (cached separately) |
| 196 | + $installer = Cache::remember('installer_status', now()->addHour(), function () { |
| 197 | + $shouldInstall = false; |
| 198 | + $shouldOnboard = false; |
| 199 | + |
| 200 | + try { |
| 201 | + DB::connection()->getPdo(); |
| 202 | + if (DB::connection()->getDatabaseName()) { |
| 203 | + if (\Illuminate\Support\Facades\Schema::hasTable('companies')) { |
| 204 | + $shouldOnboard = !DB::table('companies')->exists(); |
| 205 | + } else { |
| 206 | + $shouldInstall = true; |
| 207 | + } |
| 208 | + } else { |
| 209 | + $shouldInstall = true; |
| 210 | + } |
| 211 | + } catch (\Exception $e) { |
| 212 | + $shouldInstall = true; |
| 213 | + } |
| 214 | + |
| 215 | + return [ |
| 216 | + 'shouldInstall' => $shouldInstall, |
| 217 | + 'shouldOnboard' => $shouldOnboard, |
| 218 | + 'defaultTheme' => \Fleetbase\Models\Setting::lookup('branding.default_theme', 'dark'), |
| 219 | + ]; |
| 220 | + }); |
| 221 | + |
| 222 | + return [ |
| 223 | + 'session' => $session, |
| 224 | + 'organizations' => Organization::collection($organizations), |
| 225 | + 'installer' => $installer, |
| 226 | + ]; |
| 227 | + }); |
| 228 | + |
| 229 | + return response()->json($bootstrap) |
| 230 | + ->header('Cache-Control', 'private, max-age=300'); |
| 231 | + } |
| 232 | + |
129 | 233 | /** |
130 | 234 | * Send a verification SMS code. |
131 | 235 | * |
@@ -522,19 +626,43 @@ public function validateVerificationCode(Request $request) |
522 | 626 | */ |
523 | 627 | public function getUserOrganizations(Request $request) |
524 | 628 | { |
525 | | - $user = $request->user(); |
526 | | - $companies = Company::whereHas( |
527 | | - 'users', |
528 | | - function ($query) use ($user) { |
529 | | - $query->where('users.uuid', $user->uuid); |
530 | | - $query->whereNull('company_users.deleted_at'); |
531 | | - } |
532 | | - ) |
533 | | - ->whereHas('owner') |
534 | | - ->with(['owner', 'owner.companyUser']) |
535 | | - ->get(); |
| 629 | + $user = $request->user(); |
| 630 | + $cacheKey = "user_organizations_{$user->uuid}"; |
| 631 | + |
| 632 | + // Cache for 30 minutes |
| 633 | + $companies = Cache::remember($cacheKey, now()->addMinutes(30), function () use ($user) { |
| 634 | + // Optimized query: use join instead of whereHas |
| 635 | + return Company::select([ |
| 636 | + 'companies.uuid', |
| 637 | + 'companies.name', |
| 638 | + 'companies.owner_uuid', |
| 639 | + 'companies.created_at', |
| 640 | + 'companies.updated_at', |
| 641 | + ]) |
| 642 | + ->join('company_users', 'companies.uuid', '=', 'company_users.company_uuid') |
| 643 | + ->where('company_users.user_uuid', $user->uuid) |
| 644 | + ->whereNull('company_users.deleted_at') |
| 645 | + ->whereNotNull('companies.owner_uuid') |
| 646 | + ->with(['owner:uuid,company_uuid,name,email', 'owner.companyUser:uuid,user_uuid,company_uuid']) |
| 647 | + ->distinct() |
| 648 | + ->get(); |
| 649 | + }); |
| 650 | + |
| 651 | + return Organization::collection($companies) |
| 652 | + ->response() |
| 653 | + ->header('Cache-Control', 'private, max-age=1800'); // 30 minutes |
| 654 | + } |
536 | 655 |
|
537 | | - return Organization::collection($companies); |
| 656 | + /** |
| 657 | + * Clear user organizations cache (call when org changes). |
| 658 | + * |
| 659 | + * @param string $userUuid |
| 660 | + * |
| 661 | + * @return void |
| 662 | + */ |
| 663 | + public static function clearUserOrganizationsCache(string $userUuid) |
| 664 | + { |
| 665 | + Cache::forget("user_organizations_{$userUuid}"); |
538 | 666 | } |
539 | 667 |
|
540 | 668 | /** |
|
0 commit comments