Files
sides/docs/02-ARCHITECTURE.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

14 KiB

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

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

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

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