From 4069b09e3bfca156fd54a000a8c27adda1f521e3 Mon Sep 17 00:00:00 2001 From: sam rolfe Date: Thu, 28 Aug 2025 17:51:16 +1000 Subject: [PATCH] Initial commit: Laravel GPS API scaffolding --- app/Http/Controllers/GpsController.php | 73 +++++++++++++++++++ app/Models/GpsPoint.php | 23 ++++++ ...5_08_28_000000_create_gps_points_table.php | 28 +++++++ routes/api.php | 6 ++ 4 files changed, 130 insertions(+) create mode 100644 app/Http/Controllers/GpsController.php create mode 100644 app/Models/GpsPoint.php create mode 100644 database/migrations/2025_08_28_000000_create_gps_points_table.php create mode 100644 routes/api.php diff --git a/app/Http/Controllers/GpsController.php b/app/Http/Controllers/GpsController.php new file mode 100644 index 0000000..f75c625 --- /dev/null +++ b/app/Http/Controllers/GpsController.php @@ -0,0 +1,73 @@ +validate([ + 'device_id' => ['required','string','max:64'], + 'lat' => ['required','numeric','between:-90,90'], + 'lng' => ['required','numeric','between:-180,180'], + 'speed' => ['nullable','numeric'], + 'altitude' => ['nullable','numeric'], + 'heading' => ['nullable','integer','between:0,359'], + 'recorded_at' => ['nullable','date'], // ISO8601 preferred + 'meta' => ['nullable'], + ]); + + $data['recorded_at'] = isset($data['recorded_at']) + ? Carbon::parse($data['recorded_at']) + : now(); + + $point = GpsPoint::create($data); + + return response()->json(['id' => $point->id], 201); + } + + // GET /api/gps/latest?device_id=abc + public function latest(Request $req) + { + $req->validate(['device_id' => ['required','string']]); + + $point = GpsPoint::where('device_id', $req->device_id) + ->orderByDesc('recorded_at') + ->orderByDesc('id') + ->first(); + + if (!$point) { + return response()->json(['message' => 'Not found'], 404); + } + + return response()->json($point); + } + + // GET /api/gps/track?device_id=abc&since=2025-08-28T00:00:00Z&until=2025-08-28T23:59:59Z + public function track(Request $req) + { + $req->validate([ + 'device_id' => ['required','string'], + 'since' => ['nullable','date'], + 'until' => ['nullable','date'], + 'limit' => ['nullable','integer','between:1,10000'], + ]); + + $q = GpsPoint::where('device_id', $req->device_id); + + if ($req->since) $q->where('recorded_at', '>=', $req->since); + if ($req->until) $q->where('recorded_at', '<=', $req->until); + + $limit = $req->input('limit', 1000); + + return response()->json( + $q->orderBy('recorded_at')->orderBy('id')->limit($limit)->get() + ); + } +} \ No newline at end of file diff --git a/app/Models/GpsPoint.php b/app/Models/GpsPoint.php new file mode 100644 index 0000000..dd81049 --- /dev/null +++ b/app/Models/GpsPoint.php @@ -0,0 +1,23 @@ + 'datetime', + 'meta' => 'array', + 'lat' => 'float', + 'lng' => 'float', + 'speed' => 'float', + 'altitude' => 'float', + 'heading' => 'integer', + ]; +} \ No newline at end of file diff --git a/database/migrations/2025_08_28_000000_create_gps_points_table.php b/database/migrations/2025_08_28_000000_create_gps_points_table.php new file mode 100644 index 0000000..47d1bed --- /dev/null +++ b/database/migrations/2025_08_28_000000_create_gps_points_table.php @@ -0,0 +1,28 @@ +id(); + $table->string('device_id', 64)->index(); + $table->decimal('lat', 10, 7); + $table->decimal('lng', 10, 7); + $table->decimal('speed', 8, 2)->nullable(); // km/h or m/s + $table->decimal('altitude', 8, 2)->nullable(); // meters + $table->unsignedInteger('heading')->nullable(); // degrees + $table->timestamp('recorded_at')->index(); // when device recorded it + $table->json('meta')->nullable(); // any extra fields + $table->timestamps(); // created_at, updated_at + }); + } + + public function down(): void + { + Schema::dropIfExists('gps_points'); + } +}; \ No newline at end of file diff --git a/routes/api.php b/routes/api.php new file mode 100644 index 0000000..d705cd2 --- /dev/null +++ b/routes/api.php @@ -0,0 +1,6 @@ +use Illuminate\Support\Facades\Route; +use App\Http\Controllers\GpsController; + +Route::post('/gps', [GpsController::class, 'store']); +Route::get('/gps/latest', [GpsController::class, 'latest']); +Route::get('/gps/track', [GpsController::class, 'track']); \ No newline at end of file