Skip to content

Commit b899cd9

Browse files
author
Ronald A Richardson
committed
fix: sync user/company to sandbox before creating test API key
When a test/sandbox API key is created the request carries the Access-Console-Sandbox header, which causes SetupFleetbaseSession to switch the default database connection to 'sandbox'. The subsequent INSERT into api_credentials on the sandbox DB references user_uuid and company_uuid values that come from the production session. Because those rows do not necessarily exist in the sandbox database the foreign key constraint api_credentials_user_uuid_foreign (and the companion company_uuid constraint) fires and the insert fails with SQLSTATE[23000] 1452. Fix: override createRecord() in ApiCredentialController to detect the sandbox header and, before delegating to the generic create path, mirror the current user, company, and company_user pivot row from production into the sandbox DB using an on-demand upsert (the same pattern used by the sandbox:sync Artisan command). Foreign key checks are temporarily disabled during the upsert to avoid ordering issues, then re-enabled before the api_credentials insert proceeds.
1 parent e19c142 commit b899cd9

1 file changed

Lines changed: 131 additions & 0 deletions

File tree

src/Http/Controllers/Internal/v1/ApiCredentialController.php

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@
66
use Fleetbase\Http\Controllers\FleetbaseController;
77
use Fleetbase\Http\Requests\ExportRequest;
88
use Fleetbase\Models\ApiCredential;
9+
use Fleetbase\Models\Company;
10+
use Fleetbase\Models\CompanyUser;
11+
use Fleetbase\Models\User;
912
use Illuminate\Http\Request;
13+
use Illuminate\Support\Carbon;
1014
use Illuminate\Support\Facades\Auth;
1115
use Illuminate\Support\Facades\DB;
16+
use Illuminate\Support\Facades\Schema;
1217
use Illuminate\Support\Str;
1318
use Maatwebsite\Excel\Facades\Excel;
1419

@@ -28,6 +33,132 @@ class ApiCredentialController extends FleetbaseController
2833
*/
2934
public $service = 'developers';
3035

36+
/**
37+
* Create a new API credential record.
38+
*
39+
* Overrides the generic createRecord to ensure that when a test/sandbox key
40+
* is being created, the current user and company are mirrored into the sandbox
41+
* database first. Without this, the foreign key constraint on `api_credentials.user_uuid`
42+
* (which references `users.uuid` in the sandbox DB) will fail because the user
43+
* and company rows only exist in the production database.
44+
*
45+
* @return \Illuminate\Http\Response
46+
*/
47+
public function createRecord(Request $request)
48+
{
49+
// Determine if this is a sandbox/test key creation request.
50+
$isSandbox = \Fleetbase\Support\Utils::isTrue($request->header('Access-Console-Sandbox'));
51+
52+
if ($isSandbox) {
53+
// Ensure the current user and company exist in the sandbox DB before
54+
// attempting the insert, so the FK constraints are satisfied.
55+
$this->syncCurrentSessionToSandbox($request);
56+
}
57+
58+
return parent::createRecord($request);
59+
}
60+
61+
/**
62+
* Mirrors the currently authenticated user, their company, and the company–user
63+
* membership record into the sandbox database.
64+
*
65+
* This is a targeted, on-demand version of the `sandbox:sync` Artisan command,
66+
* scoped only to the records needed to satisfy the foreign key constraints when
67+
* inserting a new test-mode `api_credentials` row.
68+
*
69+
* @param Request $request
70+
*
71+
* @return void
72+
*/
73+
protected function syncCurrentSessionToSandbox(Request $request): void
74+
{
75+
$userUuid = session('user');
76+
$companyUuid = session('company');
77+
78+
if (!$userUuid || !$companyUuid) {
79+
return;
80+
}
81+
82+
// Temporarily disable FK checks so we can upsert in any order.
83+
Schema::connection('sandbox')->disableForeignKeyConstraints();
84+
85+
try {
86+
// --- Sync User ---
87+
$user = User::on('mysql')->withoutGlobalScopes()->where('uuid', $userUuid)->first();
88+
if ($user) {
89+
$this->upsertModelToSandbox($user);
90+
}
91+
92+
// --- Sync Company ---
93+
$company = Company::on('mysql')->withoutGlobalScopes()->where('uuid', $companyUuid)->first();
94+
if ($company) {
95+
$this->upsertModelToSandbox($company);
96+
}
97+
98+
// --- Sync CompanyUser pivot ---
99+
$companyUser = CompanyUser::on('mysql')
100+
->withoutGlobalScopes()
101+
->where('user_uuid', $userUuid)
102+
->where('company_uuid', $companyUuid)
103+
->first();
104+
if ($companyUser) {
105+
$this->upsertModelToSandbox($companyUser);
106+
}
107+
} finally {
108+
Schema::connection('sandbox')->enableForeignKeyConstraints();
109+
}
110+
}
111+
112+
/**
113+
* Upserts a single Eloquent model record into the sandbox database.
114+
*
115+
* Mirrors the approach used in the `sandbox:sync` Artisan command:
116+
* reduces the record to its fillable attributes, normalises datetime
117+
* fields to strings, JSON-encodes any Json-cast columns, then performs
118+
* an `updateOrInsert` keyed on `uuid`.
119+
*
120+
* @param \Illuminate\Database\Eloquent\Model $model
121+
*
122+
* @return void
123+
*/
124+
protected function upsertModelToSandbox(\Illuminate\Database\Eloquent\Model $model): void
125+
{
126+
$clone = collect($model->toArray())
127+
->only($model->getFillable())
128+
->toArray();
129+
130+
if (!isset($clone['uuid']) || !is_string($clone['uuid'])) {
131+
return;
132+
}
133+
134+
// Normalise datetime columns to plain strings.
135+
foreach ($clone as $key => $value) {
136+
if (isset($clone[$key]) && Str::endsWith($key, '_at')) {
137+
try {
138+
$clone[$key] = Carbon::parse($clone[$key])->toDateTimeString();
139+
} catch (\Exception $e) {
140+
$clone[$key] = null;
141+
}
142+
}
143+
}
144+
145+
// JSON-encode any Json-cast columns that are still arrays/objects.
146+
$jsonColumns = collect($model->getCasts())
147+
->filter(fn ($cast) => Str::contains($cast, 'Json'))
148+
->keys()
149+
->toArray();
150+
151+
foreach ($clone as $key => $value) {
152+
if (in_array($key, $jsonColumns) && (is_object($value) || is_array($value))) {
153+
$clone[$key] = json_encode($value);
154+
}
155+
}
156+
157+
DB::connection('sandbox')
158+
->table($model->getTable())
159+
->updateOrInsert(['uuid' => $clone['uuid']], $clone);
160+
}
161+
31162
/**
32163
* Export the companies/users api credentials to excel or csv.
33164
*

0 commit comments

Comments
 (0)