# API Reference All API endpoints are defined in `src/routes/api.php` and served under the `/api` prefix. There is no authentication middleware on any API route -- all endpoints are publicly accessible. --- ## Authentication ### POST `/api/login` Validates user credentials against the `users` table. No token or session is created; this endpoint only confirms the username and password are correct. **Request Body:** ```json { "username": "admin", "password": "" } ``` **Success Response (200):** ```json { "error": false, "id": 1, "username": "admin", "email": "admin@example.com", "acc_lvl": 1 } ``` **Error Response (200):** ```json { "error": true, "message": "Wrong Password/Username" } ``` **Implementation details:** - Uses a parameterized query (`DB::select` with `?` placeholder) to look up the user by `name` - Password verification uses `Hash::check()` against the bcrypt hash stored in the `password` column - Returns generic "Wrong Password/Username" for both unknown user and wrong password (no user enumeration) --- ## Station Data ### GET `/api/station/current` Returns all stations that have valid coordinates (`lat`/`lng` not null) joined with their latest rainfall reading, water level reading, and siren status. Each row is the most recent record from the `rainfall`, `waterlevel`, and `siren` tables per station. **Response fields per station:** | Field | Source | Description | |-------|--------|-------------| | `stationid` | `station` | Station identifier (e.g. `KBLG0026`) | | `name` | `station` | Station name | | `district` | `station` | District name | | `lng`, `lat` | `station` | GPS coordinates | | `rainfall_value` | `rainfall.hourly` | Latest hourly rainfall | | `rainfall_time` | `rainfall.timestamp` | Timestamp of latest rainfall reading | | `waterlevel_value` | `waterlevel.waterlevel` | Latest water level | | `waterlevel_time` | `waterlevel.datetime` | Timestamp of latest water level reading | | `siren_level` | `siren.level` | Latest siren level (`"N"` = Normal) | | `siren_time` | `siren.active_time` | Timestamp of latest siren event | Stations with no data across all three types (rainfall, waterlevel, siren) are excluded. ### GET `/api/station/rainfall` Returns the latest rainfall record per station. Only includes stations where the `station.rainfall` flag is set and a matching `rainfall` record exists. Returns all columns from both `station` and `rainfall` tables. ### GET `/api/station/waterlevel` Returns the latest water level record per station. Only includes stations where the `station.waterlevel` flag is set and a matching `waterlevel` record exists. Returns all columns from both `station` and `waterlevel` tables. ### GET `/api/station/notification` Returns today's latest notification per station. Joins `station` with `notification` where `notification.timestamp::date = CURRENT_DATE`, returning only the most recent notification per station for the current day. Ordered by timestamp descending. ### GET `/api/station/history` Returns notification history for the last 3 days. For each station, returns the single latest notification per day (one row per station per day). Ordered by timestamp descending. ### GET `/api/station/siren` Returns current siren status for siren-equipped stations. Joins `station` with `siren` where `station.siren = 1`, returning only the most recent siren event per station within the last 3 days. Ordered by `active_time` descending. ### GET `/api/station/siren/history` Returns siren history for the last 3 days, excluding Normal-level entries (`siren.level != "N"`). Only includes stations with `station.siren = 1`. Ordered by `active_time` descending. --- ## Alert (Push Notification) ### POST `/api/alert` Sends an FCM push notification to a topic. Called by `sidesdecode.py` when sensor thresholds are triggered. **Request Body:** ```json { "stationid": "KBLG0026", "level": "Warning", "stationtype": 1 } ``` **Parameters:** | Field | Type | Description | |-------|------|-------------| | `stationid` | string | Station identifier | | `level` | string | Alert level (e.g. `"Alert"`, `"Warning"`, `"Danger"`) | | `stationtype` | integer | Sensor type: `1` = Rainfall, `2` = Water Level, `3` = Siren | **FCM topic routing:** The controller maps `stationtype` and `level` to an FCM topic: | stationtype | level | FCM Topic | |-------------|-------|-----------| | 1 (Rainfall) | any non-"Danger" | `FCM_TOPIC_RAINFALL_WARNING` | | 1 (Rainfall) | "Danger" | `FCM_TOPIC_RAINFALL_DANGER` | | 2 (Water Level) | any non-"Danger" | `FCM_TOPIC_WATERLEVEL_ALERT` | | 2 (Water Level) | "Danger" | `FCM_TOPIC_WATERLEVEL_DANGER` | | 3 (Siren) | any | `FCM_TOPIC_RAINFALL_WARNING` | The notification title is formatted as `"{Type} {Level} Alert"` and the body as `"{stationid} : {Type} Have Triggered {Level}"`. **Response:** ```json { "status": 200 } ``` The `status` value is the HTTP status code returned by the FCM v1 API (`200` = delivered successfully). **Implementation:** - `FcmService` loads Firebase credentials from the path in `FIREBASE_CREDENTIALS` and uses `Google\Auth\Credentials\ServiceAccountCredentials` to obtain an OAuth2 access token - Sends to `https://fcm.googleapis.com/v1/projects/{FIREBASE_PROJECT_ID}/messages:send` - Android priority is set to `"high"` **Security note:** This endpoint has no authentication. Anyone with network access can trigger push notifications. --- ## Public Web Routes These are defined in `src/routes/web.php` (not under `/api`) but return JSON or perform redirects. ### GET `/stations` Returns all stations with coordinates as JSON. Used by the public-facing map. No authentication required. ### GET `/dashboard` Returns the same combined station data as `/api/station/current`. Served at the root URL `/` as well. No authentication required. ### GET `/locale/{locale}` Switches the application language. Accepts `en` or `bm`. Redirects back to the previous page.