Skip to content

Commit cf13ecb

Browse files
authored
[Feature] Added API endpoints to list, get and get the latest results (alexjustesen#1983)
1 parent a758f32 commit cf13ecb

File tree

12 files changed

+381
-63
lines changed

12 files changed

+381
-63
lines changed

app/Filament/Pages/ApiTokens.php

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace App\Filament\Pages;
44

55
use Carbon\Carbon;
6-
use Filament\Forms\Components\CheckboxList;
76
use Filament\Forms\Components\DateTimePicker;
87
use Filament\Forms\Components\TextInput;
98
use Filament\Forms\Concerns\InteractsWithForms;
@@ -25,7 +24,6 @@
2524
use Filament\Tables\Table;
2625
use Illuminate\Database\Eloquent\Relations\MorphMany;
2726
use Illuminate\Support\Facades\Auth;
28-
use Illuminate\Support\HtmlString;
2927

3028
class ApiTokens extends Page implements HasForms, HasInfolists, HasTable
3129
{
@@ -39,18 +37,8 @@ class ApiTokens extends Page implements HasForms, HasInfolists, HasTable
3937

4038
protected static ?string $navigationGroup = 'Settings';
4139

42-
protected static ?int $navigationSort = 5;
43-
4440
public ?string $token = '';
4541

46-
/**
47-
* NOTE: This page is disabled until the api feature is ready.
48-
*/
49-
public static function canAccess(): bool
50-
{
51-
return false;
52-
}
53-
5442
public function tokenInfolist(Infolist $infolist): Infolist
5543
{
5644
return $infolist
@@ -83,25 +71,16 @@ public function table(Table $table): Table
8371
TextInput::make('token_name')
8472
->label('Name')
8573
->maxLength('100')
86-
->required(),
87-
CheckboxList::make('token_abilities')
88-
->label('Abilities')
89-
->options([
90-
'result-read' => 'Read results',
91-
])
92-
->descriptions([
93-
'result-read' => new HtmlString('Allow the token to retrieve result data.'),
94-
])
9574
->required()
96-
->bulkToggleable(),
75+
->autocomplete(false),
9776
DateTimePicker::make('token_expires_at')
9877
->label('Expires at')
99-
->nullable(),
78+
->nullable()
79+
->helperText('Leave empty for no expiration'),
10080
])
10181
->action(function (array $data) {
10282
$token = Auth::user()->createToken(
10383
name: $data['token_name'],
104-
abilities: $data['token_abilities'],
10584
expiresAt: $data['token_expires_at'] ? Carbon::parse($data['token_expires_at']) : null,
10685
);
10786

app/Http/Controllers/API/HealthCheckController.php

Lines changed: 0 additions & 18 deletions
This file was deleted.

app/Http/Controllers/API/Speedtest/GetLatestController.php renamed to app/Http/Controllers/Api/V0/GetLatestController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22

3-
namespace App\Http\Controllers\API\Speedtest;
3+
namespace App\Http\Controllers\Api\V0;
44

55
use App\Enums\ResultStatus;
66
use App\Helpers\Number;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Api\V1;
4+
5+
use Illuminate\Http\Exceptions\HttpResponseException;
6+
use Illuminate\Support\Facades\Log;
7+
8+
abstract class ApiController
9+
{
10+
/**
11+
* Send a response.
12+
*
13+
* @param mixed $data
14+
* @param string $message
15+
* @param int $code
16+
* @return \Illuminate\Http\JsonResponse
17+
*/
18+
public static function sendResponse($data, $message = 'ok', $code = 200)
19+
{
20+
$response = array_filter([
21+
'data' => $data,
22+
'message' => $message,
23+
]);
24+
25+
if (! empty($message)) {
26+
$response['message'] = $message;
27+
}
28+
29+
return response()->json($response, $code);
30+
}
31+
32+
/**
33+
* Throw an exception.
34+
*
35+
* @param \Exception $e
36+
* @param int $code
37+
*
38+
* @throws \Illuminate\Http\Exceptions\HttpResponseException
39+
*/
40+
public static function throw($e, $code = 500)
41+
{
42+
Log::info($e);
43+
44+
throw new HttpResponseException(
45+
response: response()->json([
46+
'message' => $e->getMessage(),
47+
], $code)
48+
);
49+
}
50+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Api\V1;
4+
5+
use App\Http\Resources\V1\ResultResource;
6+
use App\Models\Result;
7+
use Http\Discovery\Exception\NotFoundException;
8+
use Illuminate\Http\Request;
9+
10+
class LatestResult extends ApiController
11+
{
12+
/**
13+
* Handle the incoming request.
14+
*/
15+
public function __invoke(Request $request)
16+
{
17+
$result = Result::query()
18+
->latest()
19+
->firstOr(function () {
20+
self::throw(
21+
e: new NotFoundException('No results found.'),
22+
code: 404,
23+
);
24+
});
25+
26+
return self::sendResponse(
27+
data: new ResultResource($result),
28+
);
29+
}
30+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Api\V1;
4+
5+
use App\Http\Controllers\Controller;
6+
use App\Http\Resources\V1\ResultResource;
7+
use App\Models\Result;
8+
use Illuminate\Http\Request;
9+
use Illuminate\Support\Facades\Validator;
10+
use Spatie\QueryBuilder\AllowedFilter;
11+
use Spatie\QueryBuilder\Enums\FilterOperator;
12+
use Spatie\QueryBuilder\QueryBuilder;
13+
14+
class ListResults extends Controller
15+
{
16+
/**
17+
* Handle the incoming request.
18+
*/
19+
public function __invoke(Request $request)
20+
{
21+
$validator = Validator::make($request->all(), [
22+
'per_page' => 'integer|min:1|max:500',
23+
]);
24+
25+
if ($validator->fails()) {
26+
return ApiController::sendResponse(
27+
data: $validator->errors(),
28+
message: 'Validation failed.',
29+
code: 422,
30+
);
31+
}
32+
33+
$results = QueryBuilder::for(Result::class)
34+
->allowedFilters([
35+
AllowedFilter::operator('ping', FilterOperator::DYNAMIC),
36+
AllowedFilter::operator('download', FilterOperator::DYNAMIC),
37+
AllowedFilter::operator('upload', FilterOperator::DYNAMIC),
38+
AllowedFilter::exact('healthy')->nullable(),
39+
AllowedFilter::exact('status'),
40+
AllowedFilter::exact('scheduled'),
41+
AllowedFilter::operator('created_at', FilterOperator::DYNAMIC),
42+
AllowedFilter::operator('updated_at', FilterOperator::DYNAMIC),
43+
])
44+
->allowedSorts([
45+
'ping',
46+
'download',
47+
'upload',
48+
'created_at',
49+
'updated_at',
50+
])
51+
->jsonPaginate($request->input('per_page', 25));
52+
53+
return ResultResource::collection($results);
54+
}
55+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Api\V1;
4+
5+
use App\Http\Resources\V1\ResultResource;
6+
use App\Models\Result;
7+
use Http\Discovery\Exception\NotFoundException;
8+
use Illuminate\Http\Request;
9+
10+
class ShowResult extends ApiController
11+
{
12+
/**
13+
* Handle the incoming request.
14+
*/
15+
public function __invoke(Request $request, int $id)
16+
{
17+
$result = Result::findOr($id, function () {
18+
self::throw(
19+
e: new NotFoundException('Result not found.'),
20+
code: 404,
21+
);
22+
});
23+
24+
return self::sendResponse(
25+
data: new ResultResource($result),
26+
);
27+
}
28+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace App\Http\Resources\V1;
4+
5+
use App\Helpers\Bitrate;
6+
use Illuminate\Http\Request;
7+
use Illuminate\Http\Resources\Json\JsonResource;
8+
9+
class ResultResource extends JsonResource
10+
{
11+
/**
12+
* Transform the resource into an array.
13+
*
14+
* @return array<string, mixed>
15+
*/
16+
public function toArray(Request $request): array
17+
{
18+
return [
19+
'id' => $this->id,
20+
'service' => $this->service,
21+
'ping' => $this->ping,
22+
'download' => $this->download,
23+
'upload' => $this->upload,
24+
'download_bits' => $this->when($this->download, fn (): int|float => Bitrate::bytesToBits($this->download)),
25+
'upload_bits' => $this->when($this->upload, fn (): int|float => Bitrate::bytesToBits($this->upload)),
26+
'download_bits_human' => $this->when($this->download, fn (): string => Bitrate::formatBits(Bitrate::bytesToBits($this->download)).'ps'),
27+
'upload_bits_human' => $this->when($this->upload, fn (): string => Bitrate::formatBits(Bitrate::bytesToBits($this->upload)).'ps'),
28+
'benchmarks' => $this->benchmarks,
29+
'healthy' => $this->healthy,
30+
'status' => $this->status,
31+
'scheduled' => $this->scheduled,
32+
'comments' => $this->comments,
33+
'data' => $this->data,
34+
'created_at' => $this->created_at->toDateTimestring(),
35+
'updated_at' => $this->updated_at->toDateTimestring(),
36+
];
37+
}
38+
}

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
"lorisleiva/laravel-actions": "^2.8.5",
3333
"maennchen/zipstream-php": "^2.4",
3434
"ryangjchandler/blade-tabler-icons": "^2.3",
35+
"spatie/laravel-json-api-paginate": "^1.16",
36+
"spatie/laravel-query-builder": "^6.3",
3537
"spatie/laravel-settings": "^3.4",
3638
"spatie/laravel-webhook-server": "^3.8.2",
3739
"timokoerber/laravel-one-time-operations": "^1.4.4"

0 commit comments

Comments
 (0)