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() ); } }