Skip to content

Commit 8e09d15

Browse files
authored
Merge pull request #183 from fleetbase/dev-v1.6.31
feat: Improved file download endpoint, removed query optimizer, fixed…
2 parents 9020d12 + 703dc85 commit 8e09d15

File tree

17 files changed

+738
-80
lines changed

17 files changed

+738
-80
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "fleetbase/core-api",
3-
"version": "1.6.30",
3+
"version": "1.6.31",
44
"description": "Core Framework and Resources for Fleetbase API",
55
"keywords": [
66
"fleetbase",

config/sms.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
return [
4+
/*
5+
|--------------------------------------------------------------------------
6+
| Default SMS Provider
7+
|--------------------------------------------------------------------------
8+
|
9+
| This option controls the default SMS provider that will be used to send
10+
| messages. You may set this to any of the providers defined below.
11+
|
12+
| Supported: "twilio", "callpro"
13+
|
14+
*/
15+
16+
'default_provider' => env('SMS_DEFAULT_PROVIDER', 'twilio'),
17+
18+
/*
19+
|--------------------------------------------------------------------------
20+
| SMS Provider Routing Rules
21+
|--------------------------------------------------------------------------
22+
|
23+
| Define routing rules based on phone number prefixes. When a phone number
24+
| matches a prefix, it will be routed to the specified provider.
25+
|
26+
| Format: 'prefix' => 'provider'
27+
|
28+
| Example:
29+
| '+976' => 'callpro', // Mongolia numbers route to CallPro
30+
| '+1' => 'twilio', // USA/Canada numbers route to Twilio
31+
|
32+
*/
33+
34+
'routing_rules' => [
35+
'+976' => 'callpro', // Mongolia
36+
],
37+
38+
/*
39+
|--------------------------------------------------------------------------
40+
| Throw On Error
41+
|--------------------------------------------------------------------------
42+
|
43+
| When set to true, SMS sending failures will throw exceptions. When false,
44+
| errors will be logged but the application will continue execution.
45+
|
46+
| This is useful for non-critical SMS notifications where you don't want
47+
| to interrupt the user flow if SMS delivery fails.
48+
|
49+
*/
50+
51+
'throw_on_error' => env('SMS_THROW_ON_ERROR', false),
52+
53+
/*
54+
|--------------------------------------------------------------------------
55+
| Provider Configurations
56+
|--------------------------------------------------------------------------
57+
|
58+
| Here you may configure the SMS providers used by your application.
59+
| Each provider has its own configuration options.
60+
|
61+
*/
62+
63+
'providers' => [
64+
'twilio' => [
65+
'enabled' => env('TWILIO_ENABLED', true),
66+
],
67+
68+
'callpro' => [
69+
'enabled' => env('CALLPRO_ENABLED', true),
70+
],
71+
],
72+
];

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

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,11 @@ public function session(Request $request)
115115
}
116116

117117
$sessionData = [
118-
'token' => $request->bearerToken(),
119-
'user' => $user->uuid,
120-
'verified' => $user->isVerified(),
121-
'type' => $user->getType(),
118+
'token' => $request->bearerToken(),
119+
'user' => $user->uuid,
120+
'verified' => $user->isVerified(),
121+
'type' => $user->getType(),
122+
'last_modified' => $user->updated_at,
122123
];
123124

124125
if (session()->has('impersonator')) {
@@ -132,7 +133,15 @@ public function session(Request $request)
132133
return response()->error('Session has expired.', 401, ['restore' => false]);
133134
}
134135

135-
return response()->json($session)->header('Cache-Control', 'private, max-age=300'); // 5 minutes
136+
// Generate an etag
137+
$etag = sha1(json_encode($session));
138+
139+
return response()
140+
->json($session)
141+
->setEtag($etag)
142+
->setLastModified($session['last_modified'])
143+
->header('Cache-Control', 'private, no-cache, must-revalidate')
144+
->header('X-Cache-Hit', 'false');
136145
}
137146

138147
/**
@@ -661,6 +670,7 @@ public function getUserOrganizations(Request $request)
661670
*/
662671
$etagPayload = $companies->map(function ($company) {
663672
$ownerTimestamp = $company->owner?->updated_at?->timestamp ?? 0;
673+
664674
return $company->uuid . ':' . $company->updated_at->timestamp . ':' . $ownerTimestamp;
665675
})->join('|');
666676

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

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -312,15 +312,32 @@ public function uploadBase64(UploadBase64FileRequest $request)
312312
/**
313313
* Handle file download.
314314
*
315-
* @return \Illuminate\Http\Response
315+
* Supports both:
316+
* - /download/{id}
317+
* - /download?file={id}
318+
*
319+
* @param string|null $id File UUID from route parameter
320+
*
321+
* @return \Symfony\Component\HttpFoundation\StreamedResponse
316322
*/
317-
public function download(?string $id, DownloadFileRequest $request)
323+
public function download(DownloadFileRequest $request, ?string $id = null)
318324
{
319-
$disk = $request->input('disk', config('filesystems.default'));
320-
$file = File::where('uuid', $id)->first();
325+
// Resolve file ID from route or query string
326+
$fileId = $id ?? $request->query('file');
327+
if (!$fileId) {
328+
abort(400, 'Missing file identifier.');
329+
}
330+
331+
$disk = $request->input('disk', config('filesystems.default'));
332+
$file = File::where('uuid', $fileId)->firstOrFail();
333+
$filename = $file->original_filename ?: basename($file->path);
334+
321335
/** @var \Illuminate\Filesystem\FilesystemAdapter $filesystem */
322336
$filesystem = Storage::disk($disk);
323337

324-
return $filesystem->download($file->path, $file->original_filename);
338+
return $filesystem->download(
339+
$file->path,
340+
$filename
341+
);
325342
}
326343
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@
2323
use Fleetbase\Models\User;
2424
use Fleetbase\Notifications\UserAcceptedCompanyInvite;
2525
use Fleetbase\Notifications\UserInvited;
26+
use Fleetbase\Services\UserCacheService;
2627
use Fleetbase\Support\Auth;
2728
use Fleetbase\Support\NotificationRegistry;
2829
use Fleetbase\Support\TwoFactorAuth;
2930
use Fleetbase\Support\Utils;
30-
use Fleetbase\Services\UserCacheService;
3131
use Illuminate\Http\Request;
3232
use Illuminate\Support\Arr;
3333
use Illuminate\Support\Carbon;
@@ -181,7 +181,7 @@ public function current(Request $request)
181181
$etag = UserCacheService::generateETag($user);
182182

183183
// Try to get from server cache
184-
$companyId = session('company');
184+
$companyId = session('company');
185185
$cachedData = UserCacheService::get($user->id, $companyId);
186186

187187
if ($cachedData) {
@@ -198,7 +198,7 @@ public function current(Request $request)
198198
$user->loadCompanyUser();
199199

200200
// Transform to resource
201-
$userData = new $this->resource($user);
201+
$userData = new $this->resource($user);
202202
$userArray = $userData->toArray($request);
203203

204204
// Store in cache

src/Http/Middleware/ValidateETag.php

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace Fleetbase\Http\Middleware;
44

5-
use Closure;
65
use Illuminate\Http\Request;
76

87
class ValidateETag
@@ -13,13 +12,8 @@ class ValidateETag
1312
* This middleware checks if the client sent an If-None-Match header with an ETag.
1413
* If the ETag matches the response ETag, it returns a 304 Not Modified response,
1514
* allowing the browser to use its cached version.
16-
*
17-
* @param \Illuminate\Http\Request $request
18-
* @param \Closure $next
19-
*
20-
* @return mixed
2115
*/
22-
public function handle(Request $request, Closure $next)
16+
public function handle(Request $request, \Closure $next)
2317
{
2418
// Handle the request and get response
2519
$response = $next($request);

src/Http/Requests/Internal/DownloadFileRequest.php

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,20 @@ public function authorize()
1616
return $this->session()->has('user');
1717
}
1818

19+
/**
20+
* Prepare the data for validation.
21+
*
22+
* Ensures route parameters are available for validation rules.
23+
*/
24+
protected function prepareForValidation(): void
25+
{
26+
if ($this->route('id')) {
27+
$this->merge([
28+
'id' => $this->route('id'),
29+
]);
30+
}
31+
}
32+
1933
/**
2034
* Get the validation rules that apply to the request.
2135
*
@@ -24,7 +38,9 @@ public function authorize()
2438
public function rules()
2539
{
2640
return [
27-
'id' => ['required', 'string', 'exists:files,uuid'],
41+
'file' => ['required_without:id', 'uuid', 'exists:files,uuid'],
42+
'id' => ['required_without:file', 'uuid', 'exists:files,uuid'],
43+
'disk' => ['sometimes', 'string'],
2844
];
2945
}
3046

@@ -36,8 +52,20 @@ public function rules()
3652
public function messages()
3753
{
3854
return [
39-
'id.required' => 'Please provide a file ID.',
55+
// Missing identifier
56+
'id.required_without' => 'Please provide a file identifier.',
57+
'file.required_without' => 'Please provide a file identifier.',
58+
59+
// Invalid format
60+
'id.uuid' => 'The file identifier must be a valid UUID.',
61+
'file.uuid' => 'The file identifier must be a valid UUID.',
62+
63+
// File not found
4064
'id.exists' => 'The requested file does not exist.',
65+
'file.exists' => 'The requested file does not exist.',
66+
67+
// Disk override
68+
'disk.string' => 'The storage disk must be a valid string.',
4169
];
4270
}
4371
}

src/Models/VerificationCode.php

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Fleetbase\Casts\Json;
66
use Fleetbase\Mail\VerificationMail;
7+
use Fleetbase\Services\SmsService;
78
use Fleetbase\Support\Utils;
89
use Fleetbase\Traits\Expirable;
910
use Fleetbase\Traits\HasMetaAttributes;
@@ -180,9 +181,10 @@ public static function generateSmsVerificationFor($subject, $for = 'phone_verifi
180181
$message = $messageCallback($verificationCode);
181182
}
182183

183-
// Twilio params
184-
$twilioParams = [];
184+
// SMS service options
185+
$smsOptions = [];
185186

187+
// Check for company-specific sender ID
186188
$companyUuid = data_get($options, 'company_uuid') ?? session('company') ?? data_get($subject, 'company_uuid');
187189
if ($companyUuid) {
188190
$company = Company::select(['uuid', 'options'])->find($companyUuid);
@@ -192,13 +194,33 @@ public static function generateSmsVerificationFor($subject, $for = 'phone_verifi
192194
$senderId = $company->getOption('alpha_numeric_sender_id');
193195

194196
if ($enabled && !empty($senderId)) {
195-
$twilioParams['from'] = $senderId;
197+
// Alphanumeric sender IDs are Twilio-specific
198+
// Do NOT set in $smsOptions['from'] as it would be passed to all providers
199+
$smsOptions['twilioParams']['from'] = $senderId;
196200
}
197201
}
198202
}
199203

204+
// Allow explicit provider selection
205+
$provider = data_get($options, 'provider');
206+
207+
// Send SMS using SmsService with automatic provider routing
200208
if ($subject->phone) {
201-
Twilio::message($subject->phone, $message, [], $twilioParams);
209+
try {
210+
$smsService = new SmsService();
211+
$smsService->send($subject->phone, $message, $smsOptions, $provider);
212+
} catch (\Throwable $e) {
213+
// Log error but don't fail the verification code generation
214+
\Illuminate\Support\Facades\Log::error('Failed to send SMS verification', [
215+
'phone' => $subject->phone,
216+
'error' => $e->getMessage(),
217+
]);
218+
219+
// Optionally rethrow based on configuration
220+
if (config('sms.throw_on_error', false)) {
221+
throw $e;
222+
}
223+
}
202224
}
203225

204226
return $verificationCode;

src/Observers/UserObserver.php

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@ class UserObserver
1111
{
1212
/**
1313
* Handle the User "updated" event.
14-
*
15-
* @param \Fleetbase\Models\User $user
16-
*
17-
* @return void
1814
*/
1915
public function updated(User $user): void
2016
{
@@ -46,10 +42,6 @@ public function deleted(User $user)
4642

4743
/**
4844
* Handle the User "restored" event.
49-
*
50-
* @param \Fleetbase\Models\User $user
51-
*
52-
* @return void
5345
*/
5446
public function restored(User $user): void
5547
{
@@ -66,10 +58,6 @@ public function restored(User $user): void
6658
* This clears the cached organizations list which includes owner relationships.
6759
* When a user updates their profile and they are an owner of organizations,
6860
* the cached organization data needs to be refreshed to reflect the updated owner info.
69-
*
70-
* @param \Fleetbase\Models\User $user
71-
*
72-
* @return void
7361
*/
7462
private function invalidateOrganizationsCache(User $user): void
7563
{

src/Providers/CoreServiceProvider.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ public function register()
129129
$this->mergeConfigFrom(__DIR__ . '/../../config/laravel-mysql-s3-backup.php', 'laravel-mysql-s3-backup');
130130
$this->mergeConfigFrom(__DIR__ . '/../../config/responsecache.php', 'responsecache');
131131
$this->mergeConfigFrom(__DIR__ . '/../../config/image.php', 'image');
132+
$this->mergeConfigFrom(__DIR__ . '/../../config/sms.php', 'sms');
132133

133134
// setup report schema registry
134135
$this->app->singleton(ReportSchemaRegistry::class, function () {

0 commit comments

Comments
 (0)