- 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)
5.9 KiB
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:
{
"username": "admin",
"password": "<your_password>"
}
Success Response (200):
{
"error": false,
"id": 1,
"username": "admin",
"email": "admin@example.com",
"acc_lvl": 1
}
Error Response (200):
{
"error": true,
"message": "Wrong Password/Username"
}
Implementation details:
- Uses a parameterized query (
DB::selectwith?placeholder) to look up the user byname - Password verification uses
Hash::check()against the bcrypt hash stored in thepasswordcolumn - 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:
{
"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:
{
"status": 200
}
The status value is the HTTP status code returned by the FCM v1 API (200 = delivered successfully).
Implementation:
FcmServiceloads Firebase credentials from the path inFIREBASE_CREDENTIALSand usesGoogle\Auth\Credentials\ServiceAccountCredentialsto 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.