Use firstOrCreate instead of create so db:seed can run safely on container restart without duplicate key violation.
7.7 KiB
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 monitoringwaterlevel = 1— Station has water level monitoringsiren = 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 authorizationpassword_reset_tokens— Password reset flowsessions— Database-backed sessionscache,cache_locks— Database cache storejobs,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
AdminMiddlewarecheckingaccess_level === 1 - User blocking: Users can be blocked via
is_blockedflag (though no middleware enforces it currently)