# Location Tracking System — Design Specification

**Date:** 2026-05-22
**Type:** Feature
**Status:** Draft

---

## 1. Overview

A real-time, on-demand GPS location tracking system for the KSU cooperative. Admins can trigger location capture for any field worker (employee, member, or fleet driver) with a single button click. Location data is captured silently via FCM silent push, stored with a 7-day retention policy, and displayed on a Google Map.

**Goals:**
- Admin verifies whether field workers are at their designated work sites
- Minimal user experience disruption — no visible notification to the tracked user
- Accountable — all requests and responses stored for 7 days

---

## 2. Architecture

```
Admin Dashboard           Laravel Backend               Mobile App (Field Worker)
      │                         │                              │
      │── Track Location ──────►│                              │
      │                         │── FCM Silent Push ──────────►│
      │                         │  (content-available: true)   │
      │                         │                              │
      │                         │    [App wakes in background]  │
      │                         │    [GPS captured]             │
      │                         │                              │
      │                         │◄── POST /api/location/respond  │
      │◄─ Map + Coordinates ────│    [location stored]          │
      │   [7-day retention]     │                              │
```

**Flow:**
1. Admin clicks "Track Location" for a specific user
2. Laravel sends FCM silent push (content-available, no visible notification)
3. Mobile app wakes in background, captures GPS via Geolocator
4. Mobile app POSTs location to `/api/location/respond`
5. Location stored in DB (7-day auto-deletion via expires_at + scheduled cleanup)
6. Admin sees Google Map with marker, coordinates, accuracy, and timestamp

---

## 3. Database Schema

### Table: `location_requests`

| Field | Type | Constraints | Description |
|-------|------|------------|-------------|
| `id` | UUID | PK | Primary key |
| `admin_id` | UUID | FK → users.id | Admin who triggered the request |
| `target_user_id` | UUID | FK → users.id | User being tracked |
| `created_at` | timestamp | | When request was made |
| `status` | ENUM | `pending`, `delivered`, `responded`, `failed`, `timeout` | Request state |

**Indexes:** `(target_user_id, created_at)`, `(admin_id, created_at)`

### Table: `location_responses`

| Field | Type | Constraints | Description |
|-------|------|------------|-------------|
| `id` | UUID | PK | Primary key |
| `location_request_id` | UUID | FK → location_requests.id | Parent request |
| `latitude` | DECIMAL(10,8) | NOT NULL | GPS latitude |
| `longitude` | DECIMAL(11,8) | NOT NULL | GPS longitude |
| `accuracy` | FLOAT | | Accuracy in meters |
| `timestamp` | TIMESTAMP | NOT NULL | When GPS was captured on device |
| `created_at` | TIMESTAMP | | When response was received |
| `expires_at` | TIMESTAMP | | Auto-delete marker (created_at + 7 days) |

**Indexes:** `(location_request_id)`, `(expires_at)` for cleanup job

---

## 4. API Endpoints

### `POST /api/location/track/{userId}`
**Auth:** `auth:sanctum` + `ability:admin`

Create a new location request and send silent FCM push to target user.

**Request:** None (userId from URL)

**Response (201):**
```json
{
  "id": "uuid",
  "admin_id": "uuid",
  "target_user_id": "uuid",
  "status": "pending",
  "created_at": "2026-05-22T14:35:00Z"
}
```

**Errors:**
- `403` — Not admin
- `422` — Duplicate pending request exists
- `404` — Target user not found or has no FCM token

---

### `POST /api/location/respond`
**Auth:** `auth:sanctum` (mobile app, user authenticates)

Mobile app sends GPS response after receiving silent push.

**Request:**
```json
{
  "request_id": "uuid",
  "latitude": -6.12345678,
  "longitude": 106.78901234,
  "accuracy": 15.0,
  "timestamp": "2026-05-22T14:35:30Z"
}
```

OR on error:
```json
{
  "request_id": "uuid",
  "error": "permission_denied"
}
```

Error codes: `permission_denied`, `location_unavailable`, `timeout`

**Response (200):**
```json
{
  "received": true
}
```

---

### `GET /api/location/latest/{userId}`
**Auth:** `auth:sanctum` + `ability:admin`

Get the latest location response for a user.

**Response (200):**
```json
{
  "request_id": "uuid",
  "latitude": -6.12345678,
  "longitude": 106.78901234,
  "accuracy": 15.0,
  "captured_at": "2026-05-22T14:35:30Z",
  "requested_at": "2026-05-22T14:35:00Z",
  "requested_by": "Admin Name"
}
```

**Errors:**
- `404` — No location data found

---

### `GET /api/location/history/{userId}`
**Auth:** `auth:sanctum` + `ability:admin`

Get location history for a user (last 7 days, already filtered by DB).

**Query params:** `?page=1&per_page=20`

**Response (200):**
```json
{
  "data": [
    {
      "request_id": "uuid",
      "latitude": -6.12345678,
      "longitude": 106.78901234,
      "accuracy": 15.0,
      "captured_at": "2026-05-22T14:35:30Z",
      "requested_at": "2026-05-22T14:35:00Z",
      "requested_by": "Admin Name"
    }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 20,
    "total": 5
  }
}
```

---

### `DELETE /api/location/{requestId}`
**Auth:** `auth:sanctum` + `ability:admin`

Delete a specific location request and its response.

**Response (200):**
```json
{
  "deleted": true
}
```

---

## 5. Mobile App — Silent Push Handler

**Trigger:** FCM `content-available: true` message with payload:
```json
{
  "type": "location_request",
  "request_id": "uuid",
  "timestamp": "2026-05-22T14:35:00Z"
}
```

**Handler behavior:**
1. App wakes in background (no UI, no notification to user)
2. Check location permission status via `Geolocator.checkPermission()`
3. If permission not granted → POST error response immediately
4. If granted → `Geolocator.getCurrentPosition(locationSettings)` with 30s timeout
5. POST response to `/api/location/respond`
6. Return / close background handler

**Error handling:**
- `permission_denied` — User has denied location permission
- `location_unavailable` — GPS hardware unavailable or GPS off
- `timeout` — GPS could not get fix within 30 seconds

**FCM retry:** FCM automatically retries delivery if device is temporarily offline. The 5-minute server-side timeout handles persistent failures.

---

## 6. Admin Dashboard UI

### Location Track Button
- Visible on user detail/row for admin role only
- Shows loading spinner after click
- If user already has a pending request → disabled with "Pending..." tooltip

### Map Display (on response)
- Google Maps Flutter widget
- Marker at captured coordinates
- Optional: accuracy circle overlay
- Coordinates text below map: `-6.123456, 106.789012`
- Accuracy: `±15 meters`
- Captured at: `2026-05-22 14:35 WIB`

### Location History Tab
- Per-user history showing last 7 days
- Each entry shows: map snippet, coordinates, accuracy, timestamp, requesting admin
- Pagination (20 per page)

---

## 7. Background Cleanup Job

**Laravel Console Command: `location:cleanup`**

Run daily via scheduler:
```php
$schedule->command('location:cleanup')->daily();
```

**Behavior:**
- Deletes `location_requests` rows older than 7 days (where `created_at < now() - 7 days`)
- Cascades to delete related `location_responses`
- Logs number of deleted records

---

## 8. FCM Payload

### Silent Push (content-available)
```json
{
  "to": "<device_fcm_token>",
  "content-available": 1,
  "priority": "high",
  "data": {
    "type": "location_request",
    "request_id": "uuid",
    "timestamp": "2026-05-22T14:35:00Z"
  }
}
```

---

## 9. File Structure

### Laravel Backend
```
app/
├── Http/Controllers/
│   └── LocationController.php
├── Models/
│   ├── LocationRequest.php
│   └── LocationResponse.php
├── Services/
│   ├── LocationServiceImpl.php
│   └── FCMServiceImpl.php
├── Http/Requests/
│   └── LocationRespondRequest.php
├── Jobs/
│   └── LocationRequestTimeoutJob.php
└── Console/Commands/
    └── LocationCleanup.php
database/migrations/
├── 2026_05_22_xxxxxx_create_location_requests_table.php
└── 2026_05_22_xxxxxx_create_location_responses_table.php
routes/api.php
```

### Mobile App
```
lib/
├── api/
│   └── LocationService.dart
├── models/
│   ├── location_request.dart
│   └── location_response.dart
├── store/
│   └── LocationStore.dart
├── screen/
│   └── UserDetailScreen.dart   (adds track button + map)
└── main.dart                   (adds location_request FCM handler)
```

---

## 10. Key Design Decisions

1. **UUID everywhere** — All IDs (including FK references) use UUID, consistent with existing KSU codebase patterns.
2. **Silent push over data message** — `content-available` is more reliable for background waking than data-only FCM messages.
3. **No Google Maps API key on backend** — Coordinates stored raw; frontend handles map rendering using existing `google_maps_flutter`.
4. **7-day retention + auto-cleanup** — Scheduled command cleans up old records; no manual intervention needed.
5. **5-minute request timeout** — Prevents stale pending requests; FCM retry handles transient failures.
6. **Error codes in response** — Mobile app communicates failure reasons back to admin (permission denied, GPS unavailable, etc.).
7. **No background periodic tracking** — Only triggered on-demand by admin; best battery/proximity balance.