Files
sides/docs/07-API.md
root 6863f39a24 docs: rewrite all documentation to reflect current state
- 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)
2026-05-21 02:59:32 +08:00

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.