Files
sides/docs/02-ARCHITECTURE.md
root 9122deaacd fix: seeder idempotent with firstOrCreate
Use firstOrCreate instead of create so db:seed can run safely
on container restart without duplicate key violation.
2026-05-21 02:31:47 +08:00

7.7 KiB

Architecture

Technology Stack

Layer Technology Version
Backend Laravel (PHP) 12.x
PHP PHP-FPM 8.2
Frontend Blade Templates + Alpine.js 3.x
CSS Tailwind CSS + Bootstrap 5.3 3.x / 5.3
Charts Custom JS (Chart.js via graph.js)
Maps Leaflet.js 1.9.4
Build Tool Vite 7.x
Database PostgreSQL 15
Cache/Session Database (via Laravel)
Queue Database (via Laravel)
Containerization Docker + Docker Compose 3.9
Web Server Nginx stable-alpine
PDF Generation barryvdh/laravel-dompdf 3.1
Excel Export maatwebsite/excel 3.1
Push Notifications Firebase Cloud Messaging (FCM) via Google Auth 1.49
Date Picker Flatpickr 4.6
Auth Scaffold Laravel Breeze 2.3
Data Pipeline Python (psycopg2, ftplib) 3.x

Container Architecture

The application runs in 5 Docker containers defined in docker-compose.yml:

┌──────────────────────────────────────────────────────────┐
│                    Docker Network: tckdev_net             │
│                                                          │
│  ┌─────────┐   ┌─────────┐   ┌──────────┐              │
│  │  web    │   │  app    │   │ postgres │              │
│  │ (nginx) │──>│ (PHP-FPM│──>│   (DB)   │              │
│  │ :80     │   │  :9000) │   │  :5432   │              │
│  └─────────┘   └─────────┘   └──────────┘              │
│                                                          │
│  ┌─────────┐   ┌─────────┐                              │
│  │ pgAdmin │   │ adminer │                              │
│  │ :5050   │   │ :6060   │  (DB management tools)       │
│  └─────────┘   └─────────┘                              │
└──────────────────────────────────────────────────────────┘

Container Details

Container Image Port Purpose
tckdev-app Custom (PHP 8.2-FPM + Composer 2.3 + Node.js) 9000 (internal) Laravel application
tckdev-web nginx:stable-alpine 80 Reverse proxy & static files
tckdev-db postgres:15 5432 PostgreSQL database
tckdev-pgAdmin dpage/pgadmin4 5050 Database management UI
tckdev-adminer adminer 6060 Lightweight DB management UI

Request Flow

Browser --> Nginx (:80) --> PHP-FPM (:9000) --> Laravel Router
                                                    |
                                      ┌─────────────┼─────────────────┐
                                      v             v                 v
                                  Web Routes    API Routes        Auth Routes
                                  (web.php)    (api.php)         (auth.php)
                                      |             |                 |
                                      v             v                 v
                                Controllers    Api/Controllers    Breeze Controllers
                                      |             |
                                      v             v
                                DB::raw() SQL   DB::raw() SQL
                                      |             |
                                      v             v
                                  PostgreSQL    PostgreSQL
                                      |
                                      v
                                Blade Views --> HTML Response

Middleware Stack

Middleware Route Group Purpose
auth Web (protected) Require authenticated session
admin Admin routes Require access_level = 1
LocalizationMiddleware All web routes Set locale from session (en/bm)
guest Auth routes (login/register) Redirect away if already authenticated

Database Design

Core Tables

┌─────────────┐       ┌──────────────┐
│   station   │       │    users     │
│─────────────│       │──────────────│
│ stationid*  │       │ id*          │
│ name        │       │ name         │
│ district    │       │ email        │
│ lng, lat    │       │ password     │
│ mainriverbasin│     │ access_level │
│ subriverbasin│      │ is_blocked   │
│ rainfall    │       │ login_attempts│
│ waterlevel  │       └──────────────┘
│ siren       │
│ cctv_link   │
└──────┬──────┘
       │
       │ (stationid FK via application logic, not DB constraint)
       │
  ┌────┼────────────┬──────────────┐
  v    v            v              v
┌──────────┐ ┌────────────┐ ┌───────────┐
│ rainfall │ │ waterlevel │ │   siren   │
│──────────│ │────────────│ │───────────│
│ id*      │ │ id*        │ │ id*       │
│ stationid│ │ stationid  │ │ stationid │
│ timestamp│ │ datetime   │ │ stationtype│
│ anncum   │ │ waterlevel │ │ active_time│
│ daily    │ │ alert      │ │ level     │
│ hourly   │ │ warning    │ └───────────┘
│ currentrf│ │ danger     │
│ battery  │ └────────────┘
└──────────┘
       │
       v
┌──────────────┐
│ notification │
│──────────────│
│ id*          │
│ stationid    │
│ timestamp    │
│ stationtype  │  (1=rainfall, 2=waterlevel, 3=siren)
│ level        │  (Normal, Alert, Warning, Danger)
│ active_time  │
└──────────────┘

Station Types

The station table uses boolean flags to indicate which monitoring types each station supports:

  • rainfall = 1 — Station has rainfall monitoring
  • waterlevel = 1 — Station has water level monitoring
  • siren = 1 — Station has a warning siren

Foreign Key Note

There are no database-level foreign keys between station and the data tables. Relationships are maintained at the application level via stationid column matching.

Laravel Standard Tables

The application also uses standard Laravel tables:

  • users — Authentication and authorization
  • password_reset_tokens — Password reset flow
  • sessions — Database-backed sessions
  • cache, cache_locks — Database cache store
  • jobs, job_batches, failed_jobs — Database queue

Authentication & Authorization

  • Web auth: Laravel Breeze (session-based, Blade views)
  • API auth: Custom token-less login via Api\AuthController (returns user info, no token generation)
  • Admin access: Controlled by AdminMiddleware checking access_level === 1
  • User blocking: Users can be blocked via is_blocked flag (though no middleware enforces it currently)