Use firstOrCreate instead of create so db:seed can run safely on container restart without duplicate key violation.
166 lines
7.7 KiB
Markdown
166 lines
7.7 KiB
Markdown
# 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)
|