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

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::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:

{
    "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:

  • 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.