- Remove adminer references (service was removed) - Remove mermaid diagrams (ASCII only) - Remove hardcoded credentials (use env var references) - Update all Docker references to 4-container setup (app, postgres, web, pgadmin) - Document env-based admin credentials (ADMIN_EMAIL/ADMIN_PASSWORD) - Document parameterized queries (SQL injection fixed) - Document FCM topic routing by stationtype+level - Document siren stationtype=3 fix in sidesdecode.py - Document idempotent seeder (firstOrCreate) - Document reverse proxy setup in deployment guide - Remove Makefile references (Docker Compose only)
173 lines
5.9 KiB
Markdown
173 lines
5.9 KiB
Markdown
# 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": "<your_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.
|