fix: seeder idempotent with firstOrCreate

Use firstOrCreate instead of create so db:seed can run safely
on container restart without duplicate key violation.
This commit is contained in:
root
2026-05-21 02:31:47 +08:00
parent bb8d951287
commit 9122deaacd
11 changed files with 1935 additions and 0 deletions

139
docs/07-API.md Normal file
View File

@@ -0,0 +1,139 @@
# API Reference
## Authentication
### POST `/api/login`
Login via API (returns user info, no token).
**Request Body:**
```json
{
"username": "admin",
"password": "password123"
}
```
**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"
}
```
**Notes:**
- No authentication token is generated — this endpoint only validates credentials
- No session is created — API endpoints are stateless
- All other API endpoints have **no authentication** — they are publicly accessible
---
## Station Data
### GET `/api/station/current`
Returns all stations with their latest rainfall, water level, and siren data.
**Response:**
```json
[
{
"stationid": "KBLG0026",
"name": "Stesen Kupang",
"district": "Baling",
"lng": 100.7521,
"lat": 5.7879,
"rainfall": 1,
"waterlevel": 1,
"siren": 1,
"rainfall_value": 12.5,
"rainfall_time": "2025-11-06T14:00:00",
"waterlevel_value": 3.2,
"waterlevel_time": "2025-11-06T14:00:00",
"siren_level": "N",
"siren_time": "2025-11-06T14:00:00"
}
]
```
### GET `/api/station/rainfall`
Returns latest rainfall data for rainfall-enabled stations.
### GET `/api/station/waterlevel`
Returns latest water level data for waterlevel-enabled stations.
### GET `/api/station/notification`
Returns today's latest notification per station.
### GET `/api/station/history`
Returns notification history for the last 3 days (latest per station per day).
### GET `/api/station/siren`
Returns current siren status for siren-equipped stations (last 3 days).
### GET `/api/station/siren/history`
Returns siren history for the last 3 days (excluding Normal level).
---
## Alert (Push Notification)
### POST `/api/alert`
Sends an FCM push notification. Called by the Python autoscript.
**Request Body:**
```json
{
"stationid": "KBLG0026",
"level": "Warning",
"stationtype": 1
}
```
**Parameters:**
| Field | Type | Description |
|-------|------|-------------|
| `stationid` | string | Station identifier |
| `level` | string | Alert level: "Alert", "Warning", "Danger" |
| `stationtype` | integer | 1=Rainfall, 2=Water Level, 3=Siren |
**Response:**
```json
{
"status": 200
}
```
The `status` is the HTTP status code returned by the FCM API (200 = success).
---
## Public Web API
### GET `/stations`
Returns all stations with coordinates as JSON (used by the public map).
### GET `/locale/{locale}`
Switches language. Valid values: `en`, `bm`. Redirects back to previous page.