<!-- generated-by: gsd-doc-writer -->

# Architecture

## System Overview

SIDES (Sistem Informasi Data dan Early Warning System) is a Laravel 12 web application that collects, stores, and displays real-time environmental monitoring data from rainfall, water level, and siren stations. It provides a dashboard with a map view of station status, historical data with export capabilities (Excel, PDF), and push notifications via Firebase Cloud Messaging (FCM). The system runs entirely in Docker containers using nginx as a reverse proxy, PHP-FPM for application code, and PostgreSQL 16 for persistence. External IoT stations submit readings through a public REST API; authenticated admin users manage stations and user accounts through a Blade-based web UI.

## Container Architecture

```
                        +------------------+
                        |   User Browser   |
                        +--------+---------+
                                 |
                                 | :80
                                 v
                        +--------+---------+
                        |  web (nginx)     |
                        |  nginx:stable-   |
                        |    alpine        |
                        +--------+---------+
                                 |
                          fastcgi :9000
                                 |
                        +--------+---------+
          ./src ----->  |  app (php-fpm)   |
          (bind mount)  |  php:8.2-fpm     |
                        +--------+---------+
                                 |
                          pgsql :5432
                                 |
                        +--------+---------+     +------------------+
                        |  postgres        |     |  pgadmin         |
                        |  postgres:16     |     |  dpage/pgadmin4  |
                        +------------------+     +------------------+
                          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                                    |
|------------|---------------------|--------|--------------------------------------------|
| tckdev-app | php:8.2-fpm (custom)| 9000   | Laravel application (PHP-FPM)              |
| tckdev-db  | postgres:16         | 5432   | PostgreSQL database                        |
| tckdev-web | nginx:stable-alpine | 80     | Reverse proxy, serves static assets        |
| tckdev-pgAdmin | dpage/pgadmin4  | 5050   | Database administration UI                 |

### Networking and Volumes

- All containers share the `tckdev_net` bridge network.
- `src/` is bind-mounted into both `app` and `web` at `/var/www/html`, so code changes are reflected without rebuilding.
- `pgdata` and `pgadmin_data` are Docker named volumes -- not bind mounts.
- The nginx config is bind-mounted from `docker/nginx/default.conf`.
- `docker/backup` is bind-mounted into pgadmin at `/backups`.

### Entrypoint Behaviour

The `app` container runs `docker/entrypoint.sh` before starting PHP-FPM. This script:

1. Copies `.env.example` to `.env` if no `.env` exists.
2. Runs `composer install` if `vendor/` is missing.
3. Runs `npm install` if `node_modules/` is missing.
4. Runs `npm run build` (Vite) if `public/build/` is missing.
5. Runs `php artisan migrate --force` when `RUN_MIGRATIONS=true`.
6. Runs `php artisan db:seed --force` when `RUN_SEEDER=true`.
7. Caches config, routes, and views for production performance.

## Request Flow

### Web UI Request (Blade pages)

```
Browser --:80--> nginx
                  |
                  +-- static asset? --> /var/www/html/public/{file}
                  |
                  +-- *.php? ---------> fastcgi://app:9000
                                          |
                                          Laravel Router
                                            |
                                            +-- auth middleware --> Breeze session auth
                                            +-- admin middleware --> access_level === 1 check
                                            |
                                            Controller
                                              |
                                              DB::table() / DB::select()
                                                |
                                                v
                                              PostgreSQL
                                                |
                                                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`.
4. Laravel routes the request through middleware (`auth`, `admin`, or none).
5. Controllers query PostgreSQL using `DB::table()` or `DB::select()` with parameterized bindings.
6. Responses are rendered Blade views (HTML) or JSON for AJAX endpoints.

### API Request (IoT station data)

```
IoT Device --:80/api/...--> nginx ---> app:9000
                                        |
                                        Laravel API Router (no auth middleware on data endpoints)
                                          |
                                          Controller (Api\*)
                                            |
                                            DB::table() / DB::select()
                                              |
                                              v
                                            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

The application uses six database tables. There are no foreign key constraints; relationships are enforced at the application level. `User` is the only Eloquent model. All other data access uses the `DB` query builder with parameterized queries.

```
+----------------+       +----------------+       +----------------+
|    station     |       |   rainfall     |       |   waterlevel   |
|----------------|       |----------------|       |----------------|
| stationid (PK) |<--+---| id (PK)        |  +----| id (PK)        |
| name           |   |   | stationid      |  |    | stationid      |
| district       |   |   | timestamp      |  |    | datetime       |
| lng            |   |   | anncum         |  |    | waterlevel     |
| lat            |   |   | daily          |  |    | alert          |
| mainriverbasin |   |   | hourly         |  |    | warning        |
| subriverbasin  |   |   | currentrf      |  |    | danger         |
| rainfall (flag)|   |   | battery        |  |    +----------------+
| waterlevel (f) |   |   +----------------+  |
| siren (flag)   |   |                       |
| cctv_link      |   |   +----------------+  |   +----------------+
+----------------+   |   |   siren        |  |   | notification   |
                     +---| id (PK)        |  +---| id (PK)        |
                         | stationid      |      | stationid      |
                         | stationtype    |      | timestamp      |
                         | active_time    |      | stationtype    |
                         | level          |      | level          |
                         +----------------+      | active_time    |
                                                 +----------------+
+----------------+
|     users      |
|----------------|
| id (PK)        |
| name           |
| email          |
| access_level   |
| password       |
| login_attempts |
| is_blocked     |
+----------------+
```

```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                                                |
|---------------|--------------------------------------------------------|
| `station`     | Master list of monitoring stations with location and capability flags |
| `rainfall`    | Timestamped rainfall readings per station              |
| `waterlevel`  | Timestamped water level readings with alert/warning/danger thresholds |
| `siren`       | Siren activation events with level (N/D/W/A)           |
| `notification`| Notification log entries linking stations to alert levels |
| `users`       | Application users; `access_level=1` for admin          |

## Application Layer

### Controllers

**Web controllers** (render Blade views):

| Controller              | Responsibility                                        |
|-------------------------|-------------------------------------------------------|
| `MapController`         | Dashboard/map view; joins station with latest readings |
| `RainfallController`    | Rainfall display, graphs, historical data, Excel export |
| `WaterLevelController`  | Water level display, graphs, historical data, Excel export |
| `SirenController`       | Siren status display, history, PDF export              |
| `NotificationController`| Rainfall/water level/siren notification views and PDF exports |
| `AdminController`       | Station and user CRUD (admin-only)                     |
| `cctvController`        | CCTV feed display                                     |
| `LocaleController`      | Language switching                                    |
| `ProfileController`     | User profile management (Breeze)                      |
| Auth controllers        | Login, register, password reset (Breeze scaffolding)  |

**API controllers** (return JSON):

| Controller              | Responsibility                                        |
|-------------------------|-------------------------------------------------------|
| `Api\StationController` | Current data, rainfall, water level, notifications, siren status for IoT/external consumption |
| `Api\AuthController`    | Token-less login; validates username/password, returns user JSON |
| `Api\AlertController`   | Routes FCM push notifications to the correct topic based on stationtype and level |

### Middleware

| Middleware              | Behavior                                              |
|-------------------------|-------------------------------------------------------|
| `auth`                  | Redirects unauthenticated users to login (Breeze)     |
| `admin`                 | Checks `access_level === 1`; redirects non-admins to dashboard |
| `LocalizationMiddleware`| Sets application locale from session                   |

### Services

| Service       | Responsibility                                               |
|---------------|--------------------------------------------------------------|
| `FcmService`  | Obtains Google OAuth2 access token from Firebase service account credentials, sends FCM v1 API messages to topics |

### FCM Topic Routing

`AlertController::send()` determines the FCM topic from the request's `stationtype` and `level`:

| stationtype | Level    | FCM Topic                 |
|-------------|----------|---------------------------|
| 1           | Warning  | `FCM_TOPIC_RAINFALL_WARNING`  |
| 1           | Danger   | `FCM_TOPIC_RAINFALL_DANGER`   |
| 2           | Warning  | `FCM_TOPIC_WATERLEVEL_ALERT`  |
| 2           | Danger   | `FCM_TOPIC_WATERLEVEL_DANGER` |
| 3           | (any)    | `FCM_TOPIC_RAINFALL_WARNING`  |

## Authentication

Two separate authentication mechanisms coexist:

- **Web UI**: Laravel Breeze provides session-based authentication (login, register, password reset, email verification). The `auth` middleware guards protected routes.
- **API**: `POST /api/login` accepts `username` and `password`, validates against the `users` table, and returns user data as JSON. There is no token or session persisted for API consumers -- each API data endpoint is stateless and unauthenticated.

## Frontend Stack

- Blade templates with Tailwind CSS (via `tailwind.config.js`).
- Vite handles asset bundling (`vite.config.js`, `postcss.config.js`).
- Map rendering uses Leaflet with station markers.
- Client-side JavaScript in `public/js/` handles graph rendering and AJAX calls.

## Database Configuration

- **Connection**: `pgsql` (PostgreSQL 16) -- configured in `.env` as `DB_CONNECTION=pgsql`.
- **Host**: `postgres` (Docker service name).
- **Session driver**: `database` (Laravel's database session storage).
- **Cache store**: `database`.
- **Queue connection**: `database`.

All session, cache, and queue data lives in PostgreSQL tables created by Laravel's default migrations (`cache`, `jobs`, `sessions` tables).

## Directory Structure

```
tckdev/
  docker/                  Docker configuration
    entrypoint.sh          App container startup script
    nginx/
      default.conf         nginx reverse proxy config
  src/                     Laravel application (bind-mounted into containers)
    app/
      Http/
        Controllers/       Web controllers
        Controllers/Api/   API controllers (StationController, AuthController, AlertController)
        Middleware/        AdminMiddleware, LocalizationMiddleware
        Requests/          Form request validation
      Exports/             Maatwebsite Excel export classes
      Models/              User (only Eloquent model)
      Notifications/       ResetPasswordNotification
      Services/            FcmService
      Providers/           AppServiceProvider
      View/Components/     AppLayout, GuestLayout (Blade components)
    bootstrap/
    config/                Laravel config files
    database/
      migrations/          Schema migrations (13 files)
      seeders/             Database seeders
      factories/           Model factories
    public/                Document root (served by nginx)
    resources/
      views/               Blade templates
    routes/
      web.php              Web routes (authenticated + admin + public)
      api.php              API routes (stateless, no auth middleware)
      auth.php             Breeze auth routes
      console.php          Artisan commands
    storage/               App storage (logs, firebase credentials, uploads)
    tests/                 PHPUnit tests
  docker-compose.yml       Service definitions (Compose v2, no version key)
  Dockerfile               Custom php:8.2-fpm image
```
