diff --git a/docs/01-OVERVIEW.md b/docs/01-OVERVIEW.md index 6d4f0c1c..bd3e06a4 100644 --- a/docs/01-OVERVIEW.md +++ b/docs/01-OVERVIEW.md @@ -45,6 +45,33 @@ SIDES (Sabo Integrated Debris Flow Monitoring and Early Warning System) is a web +---------+ ``` +```mermaid +flowchart TD + I["Internet"]:::internet --> RP["Reverse Proxy (TLS termination)"]:::proxy + RP --> NET["Docker Network (tckdev_net)"]:::network + + subgraph NET["Docker Network"] + N["nginx (tckdev-web :80)"]:::server --> P["php-fpm (tckdev-app :9000)"]:::app --> DB["PostgreSQL (tckdev-db :5432)"]:::db + PG["pgAdmin (tckdev-pgAdmin :5050)"]:::tool --> DB + V["pgdata volume"]:::volume --> DB + end + + P --> L["Laravel 12 Application"]:::laravel + L --> F["FCM API"]:::api + + classDef internet fill:#2196F3,stroke:#333,stroke-width:2px,color:#fff; + classDef proxy fill:#4CAF50,stroke:#333,stroke-width:2px,color:#fff; + classDef network fill:#9C27B0,stroke:#333,stroke-width:2px,color:#fff; + classDef server fill:#FF9800,stroke:#333,stroke-width:2px,color:#fff; + classDef app fill:#00BCD4,stroke:#333,stroke-width:2px,color:#fff; + classDef db fill:#673AB7,stroke:#333,stroke-width:2px,color:#fff; + classDef tool fill:#FFC107,stroke:#333,stroke-width:2px,color:#000; + classDef volume fill:#795548,stroke:#333,stroke-width:2px,color:#fff; + classDef laravel fill:#8BC34A,stroke:#333,stroke-width:2px,color:#fff; + classDef api fill:#607D8B,stroke:#333,stroke-width:2px,color:#fff; + +``` + All four services run as Docker containers on a single host: | Container | Image | Purpose | diff --git a/docs/02-ARCHITECTURE.md b/docs/02-ARCHITECTURE.md index 38784723..424c1778 100644 --- a/docs/02-ARCHITECTURE.md +++ b/docs/02-ARCHITECTURE.md @@ -37,6 +37,21 @@ SIDES (Sistem Informasi Data dan Early Warning System) is a Laravel 12 web appli volume: pgdata volume: pgadmin_data ``` +```mermaid +flowchart TD + UB["User Browser"]:::client -->|:80| N["web (nginx)\nnginx:stable-alpine"]:::server + N -->|fastcgi :9000| A["app (php-fpm)\nphp:8.2-fpm\n(bind mount ./src)"]:::app + A -->|pgsql :5432| DB["postgres\npostgres:16\nvolume: pgdata"]:::db + PG["pgAdmin\ndpage/pgadmin4\nvolume: pgadmin_data"]:::tool --> DB + + classDef client fill:#2196F3,stroke:#333,stroke-width:2px,color:#fff; + classDef server fill:#FF9800,stroke:#333,stroke-width:2px,color:#fff; + classDef app fill:#00BCD4,stroke:#333,stroke-width:2px,color:#fff; + classDef db fill:#673AB7,stroke:#333,stroke-width:2px,color:#fff; + classDef tool fill:#FFC107,stroke:#333,stroke-width:2px,color:#000; + +``` + ### Container Summary | Container | Image | Port | Purpose | @@ -92,6 +107,34 @@ Browser --:80--> nginx Blade view / JSON response ``` +```mermaid +flowchart TD + B["Browser"]:::client -->|:80| N["nginx"]:::server + + N -->|static asset?| SA["/var/www/html/public/{file}"]:::static + N -->|*.php?| P["fastcgi://app:9000"]:::php + + P --> R["Laravel Router"]:::router + + R --> AM["auth middleware\n(Breeze session auth)"]:::middleware + R --> ADM["admin middleware\n(access_level === 1 check)"]:::middleware + R --> C["Controller"]:::controller + + C --> Q["DB::table() / DB::select()"]:::query --> DB["PostgreSQL"]:::db + DB --> V["Blade view / JSON response"]:::response + + classDef client fill:#2196F3,stroke:#333,stroke-width:2px,color:#fff; + classDef server fill:#FF9800,stroke:#333,stroke-width:2px,color:#fff; + classDef static fill:#9E9E9E,stroke:#333,stroke-width:2px,color:#fff; + classDef php fill:#00BCD4,stroke:#333,stroke-width:2px,color:#fff; + classDef router fill:#9C27B0,stroke:#333,stroke-width:2px,color:#fff; + classDef middleware fill:#FFC107,stroke:#333,stroke-width:2px,color:#000; + classDef controller fill:#4CAF50,stroke:#333,stroke-width:2px,color:#fff; + classDef query fill:#795548,stroke:#333,stroke-width:2px,color:#fff; + classDef db fill:#673AB7,stroke:#333,stroke-width:2px,color:#fff; + classDef response fill:#607D8B,stroke:#333,stroke-width:2px,color:#fff; +``` + 1. Browser hits port 80 on the `web` container. 2. nginx serves static files from `/var/www/html/public` directly. 3. PHP requests are forwarded via FastCGI to `app:9000`. @@ -114,6 +157,24 @@ IoT Device --:80/api/...--> nginx ---> app:9000 PostgreSQL ``` +```mermaid +flowchart TD + D["IoT Device"]:::device -->|:80/api/...| N["nginx"]:::server + N --> P["app:9000 (php-fpm)"]:::php + P --> R["Laravel API Router\n(no auth middleware on data endpoints)"]:::router + R --> C["Controller (Api\\*)"]:::controller + C --> Q["DB::table() / DB::select()"]:::query --> DB["PostgreSQL"]:::db + + classDef device fill:#2196F3,stroke:#333,stroke-width:2px,color:#fff; + classDef server fill:#FF9800,stroke:#333,stroke-width:2px,color:#fff; + classDef php fill:#00BCD4,stroke:#333,stroke-width:2px,color:#fff; + classDef router fill:#9C27B0,stroke:#333,stroke-width:2px,color:#fff; + classDef controller fill:#4CAF50,stroke:#333,stroke-width:2px,color:#fff; + classDef query fill:#795548,stroke:#333,stroke-width:2px,color:#fff; + classDef db fill:#673AB7,stroke:#333,stroke-width:2px,color:#fff; +``` + + API routes under `/api/` have no middleware. The `POST /api/login` endpoint validates credentials against the `users` table using `DB::select()` with bound parameters and `Hash::check()`. There are no API tokens -- authentication is a single request/response returning user data on success. ## Data Model @@ -156,6 +217,77 @@ The application uses six database tables. There are no foreign key constraints; +----------------+ ``` +```mermaid +erDiagram + station { + int stationid + string name + string district + float lng + float lat + string mainriverbasin + string subriverbasin + boolean rainfall_flag + boolean waterlevel_flag + boolean siren_flag + string cctv_link + } + + rainfall { + int id + int stationid + datetime timestamp + float anncum + float daily + float hourly + float currentrf + float battery + } + + waterlevel { + int id + int stationid + datetime datetime + float waterlevel + string alert + string warning + string danger + } + + siren { + int id + int stationid + string stationtype + datetime active_time + string level + } + + notification { + int id + int stationid + datetime timestamp + string stationtype + string level + datetime active_time + } + + users { + int id + string name + string email + string access_level + string password + int login_attempts + boolean is_blocked + } + + station ||--o{ rainfall : "has" + station ||--o{ waterlevel : "has" + station ||--o{ siren : "has" + station ||--o{ notification : "has" +``` + + ### Table Descriptions | Table | Purpose | diff --git a/docs/06-DATABASE.md b/docs/06-DATABASE.md index 8eb788b1..e27284e2 100644 --- a/docs/06-DATABASE.md +++ b/docs/06-DATABASE.md @@ -61,6 +61,67 @@ persistent storage. Data survives container restarts and rebuilds. |--------------| ``` +```mermaid +erDiagram + station { + int stationid + string name + string district + float lng + float lat + string mainriverbasin + string subriverbasin + float rainfall + float waterlevel + boolean siren + string cctv_link + } + + rainfall { + int id + int stationid + datetime timestamp + float anncum + float daily + float hourly + float currentrf + float battery + } + + waterlevel { + int id + int stationid + datetime datetime + float waterlevel + string alert + string warning + string danger + } + + siren { + int id + int stationid + string stationtype + datetime active_time + string level + } + + notification { + int id + int stationid + datetime timestamp + string stationtype + string level + datetime active_time + } + + station ||--o{ rainfall : "has" + station ||--o{ waterlevel : "has" + station ||--o{ siren : "has" + station ||--o{ notification : "has" +``` + + **Note:** There are no database-level foreign keys between `station` and the data tables. Relationships are enforced at the application level by matching `stationid` values.