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)
This commit is contained in:
@@ -1,165 +1,286 @@
|
||||
<!-- generated-by: gsd-doc-writer -->
|
||||
|
||||
# Architecture
|
||||
|
||||
## Technology Stack
|
||||
## System Overview
|
||||
|
||||
| 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 |
|
||||
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
|
||||
|
||||
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) │
|
||||
│ └─────────┘ └─────────┘ │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
+------------------+
|
||||
| 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 Details
|
||||
### Container Summary
|
||||
|
||||
| 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 |
|
||||
| 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
|
||||
|
||||
```
|
||||
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
|
||||
### Web UI Request (Blade pages)
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────────┐
|
||||
│ 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 │
|
||||
└──────────────┘
|
||||
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
|
||||
```
|
||||
|
||||
### Station Types
|
||||
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.
|
||||
|
||||
The `station` table uses boolean flags to indicate which monitoring types each station supports:
|
||||
### API Request (IoT station data)
|
||||
|
||||
- `rainfall = 1` — Station has rainfall monitoring
|
||||
- `waterlevel = 1` — Station has water level monitoring
|
||||
- `siren = 1` — Station has a warning siren
|
||||
```
|
||||
IoT Device --:80/api/...--> nginx ---> app:9000
|
||||
|
|
||||
Laravel API Router (no auth middleware on data endpoints)
|
||||
|
|
||||
Controller (Api\*)
|
||||
|
|
||||
DB::table() / DB::select()
|
||||
|
|
||||
v
|
||||
PostgreSQL
|
||||
```
|
||||
|
||||
### Foreign Key Note
|
||||
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.
|
||||
|
||||
There are **no database-level foreign keys** between `station` and the data tables. Relationships are maintained at the application level via `stationid` column matching.
|
||||
## Data Model
|
||||
|
||||
### Laravel Standard Tables
|
||||
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.
|
||||
|
||||
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
|
||||
```
|
||||
+----------------+ +----------------+ +----------------+
|
||||
| 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 |
|
||||
+----------------+
|
||||
```
|
||||
|
||||
## Authentication & Authorization
|
||||
### Table Descriptions
|
||||
|
||||
- **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)
|
||||
| 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
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user