# Architecture **Analysis Date:** 2026-05-28 ## System Overview This is a **Laravel 12 MVC** application — the **Sabo Integrated Debris Flow Monitoring and Early Warning System (SIDES)** for Sungai Kupang, Baling, Malaysia. It monitors rainfall, water level, and siren station data from debris flow monitoring stations, serves a dashboard with Leaflet maps + Chart.js graphs, sends Firebase Cloud Messaging (FCM) alerts, and exports data to Excel/PDF. ```text ┌─────────────────────────────────────────────────────────────────────┐ │ Presentation Layer │ │ Blade Layouts (layout.app, layout.homeapp) / PDF Templates / JSON │ │ `resources/views/layout/`, `resources/views/pdf/` │ ├──────────────────┬────────────────────┬─────────────────────────────┤ │ Web Routes │ API Routes │ Console / Artisan │ │ `routes/web.php`│ `routes/api.php` │ `routes/console.php` │ ├─────────┬────────┴─────────┬──────────┴──────────┬──────────────────┤ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌────────────┐ ┌──────────────┐ ┌───────────────────┐ │ │ │Controllers │ │ Auth Ctrlrs │ │ API Controllers │ │ │ │ Web │ │ (Breeze) │ │ (JSON responses) │ │ │ └──────┬─────┘ └──────┬───────┘ └────────┬──────────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ Service Layer │ │ │ │ FcmService (`app/Services/FcmService.php`) │ │ │ │ → Firebase Cloud Messaging (topic-based push) │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ Eloquent Model Layer (minimal — only User model used) │ │ │ │ All other data access via DB::table() / DB::select() │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ PostgreSQL Database │ │ │ │ Tables: users, station, rainfall, waterlevel, siren, │ │ │ │ notification, password_reset_tokens, sessions, │ │ │ │ cache, jobs, job_batches │ │ │ └────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘ ``` ## Component Responsibilities | Component | Responsibility | File(s) | |-----------|----------------|---------| | Web Controllers | Serve Blade views with paginated/aggregated station data | `app/Http/Controllers/{Map,Rainfall,WaterLevel,Siren,Notification,cctv,Admin,Profile,Locale}Controller.php` | | Auth Controllers | Login/Register/Password Reset/Verify Email via Laravel Breeze | `app/Http/Controllers/Auth/*.php` (9 files) | | API Controllers | Return JSON for external IoT/second-screen consumption | `app/Http/Controllers/Api/{Station,Auth,Alert}Controller.php` | | MapController | Dashboard entry — joins station + latest rainfall + latest waterlevel + latest siren | `app/Http/Controllers/MapController.php` | | AdminController | CRUD for stations and users (access_level=1 gate) | `app/Http/Controllers/AdminController.php` | | FcmService | Google OAuth2 token fetch → FCM v1 HTTP API topic push | `app/Services/FcmService.php` | | Export Layer | Excel (Maatwebsite) + PDF (DomPDF) generation | `app/Exports/*.php`, `resources/views/pdf/*.blade.php` | | Middleware | Authentication gate + admin role check + locale | `app/Http/Middleware/{Admin,Localization}Middleware.php` | ## Pattern Overview **Overall:** Model-View-Controller with heavy use of raw SQL (PostgreSQL dialect) and **zero Eloquent ORM outside the User model**. **Key Characteristics:** - All database queries use `DB::table()` or `DB::select()` — not Eloquent models - Only `User` model (`app/Models/User.php`) extends `Authenticatable` — used for auth + password reset notification override - Controllers return Blade views (`view()`) for web, JSON (`response()->json()`) for API - No repository/DAO abstraction layer — queries live directly in controllers - Single service class (`FcmService`) handles Firebase Cloud Messaging - Localization via session-based middleware with two locales: English (`en`) and Bahasa Malaysia (`bm`) ## Layers **Web Controllers (Presentation + Logic):** - Purpose: Handle HTTP requests, query data via raw SQL, return Blade views or JSON - Location: `app/Http/Controllers/` (11 files) + `app/Http/Controllers/Auth/` (9 files) - Contains: Request handling, SQL queries, view/JSON responses, form validation (inline `$request->validate()`) - Depends on: `DB` facade, `Carbon`, `Excel`/`Pdf` facades, `FcmService` (AlertController only) - Used by: Web routes (`routes/web.php`, `routes/auth.php`) **API Controllers:** - Purpose: External data endpoints for mobile/IoT clients, always return JSON - Location: `app/Http/Controllers/Api/` (3 files) - Contains: Station data queries, custom auth (username/password via raw SQL), FCM alert proxy - Used by: API routes (`routes/api.php`) - Key difference from web controllers: No middleware/auth gates on most API routes (except login) **Service Layer:** - Purpose: Encapsulate external service integrations - Location: `app/Services/FcmService.php` - Contains: Firebase OAuth2 token generation, FCM v1 HTTP API calls - Dependencies: `google/auth` package, Laravel `Http` facade **Export Layer:** - Purpose: Generate downloadable files (Excel, PDF) - Location: `app/Exports/*.php` + `resources/views/pdf/*.blade.php` - Contains: Excel exports using `Maatwebsite\Excel` with `FromCollection`, `WithHeadings`, `ShouldAutoSize`; PDF exports using `Barryvdh\DomPDF` - Patterns: Export classes receive station/date parameters via constructor, queries data in `collection()`, defines headings in `headings()` **View Layer:** - Purpose: Render HTML and PDF output - Location: `resources/views/` - Contains: Blade layouts (`layout.app`, `layout.homeapp`), section-based views, component templates, PDF templates - Organization: Layouts in `layout/`, auth screens in `auth/`, nav in `nav/`, shared components in `components/` - Key inheritance: `layout.homeapp` → `layout.home` (public); `layout.app` → all authenticated pages ## Data Flow ### Primary Request Path — Dashboard Page 1. Request hits `GET /dashboard` or `GET /` — mapped in `routes/web.php:60-61` 2. `MapController::getCurrentData()` `app/Http/Controllers/MapController.php:23` 3. Executes a raw query joining `station` with latest `rainfall`, `waterlevel`, `siren` records using subqueries 4. Returns `view('layout.dashboard', compact('data'))` 5. Blade view extends `layout.app` which includes `nav.header` (head, CSS, Leaflet), `nav.navbar` (navigation), and sections `content1` (station data table + map) and `content2` (info section) 6. Response includes embedded JavaScript with `window.translations` for i18n and Leaflet map rendering ### Auth Flow — Login 1. `POST /login` → `AuthenticatedSessionController::store()` `app/Http/Controllers/Auth/AuthenticatedSessionController.php:55` 2. Uses `LoginRequest` `app/Http/Requests/Auth/LoginRequest.php` for rate limiting (5 attempts) 3. Custom pre-check: queries `User::where('name', $name)` checks `is_blocked` flag 4. If blocked → returns error; if `login_attempts >= 3` → auto-block on failed attempt 5. If success → reset `login_attempts`, regenerate session, redirect to `/` 6. Login page (GET /login) also renders the dashboard map with station data (`create()` method) ### Alert Flow — External IoT Alert → FCM Push 1. External system `POST /api/alert` → `api.php:18` → `AlertController::send()` `app/Http/Controllers/Api/AlertController.php:17` 2. Receives `stationid`, `level`, `stationtype` (1=Rainfall, 2=WaterLevel, 3=Siren) 3. Injects `FcmService` via constructor 4. Calls `$this->fcm->sendToTopic($topic, $title, $body)` 5. `FcmService::sendToTopic()` `app/Services/FcmService.php:23`: - Reads `FIREBASE_PROJECT_ID` and `FIREBASE_CREDENTIALS` from env - Loads service account JSON, fetches OAuth2 token via `google/auth` - POSTs to `https://fcm.googleapis.com/v1/projects/{projectId}/messages:send` - Returns HTTP status code ### Export Flow — Download Excel 1. `GET /rainfall/historical/export` → `RainfallController::exportHourlyRainfallExcel()` `app/Http/Controllers/RainfallController.php:360` 2. Reads query params (`station`, `startdate`, `enddate`) 3. Instantiates `HourlyRainfallExport($stationid, $startDate, $endDate)` `app/Exports/HourlyRainfallExport.php` 4. Export class queries PostgreSQL with pivot (24 hour columns) and returns Excel download ### Export Flow — Download PDF 1. `GET /export/rainfall-history/pdf` → `NotificationController::exportHistoryRfPDF()` `app/Http/Controllers/NotificationController.php:91` 2. Queries `notification` join `station` where `stationtype=1` 3. `Pdf::loadView('pdf.rfhistory', compact('rfHistory'))` → renders `resources/views/pdf/rfhistory.blade.php` 4. Returns PDF download response **State Management:** - Session-based auth state (standard Laravel session driver) - Locale stored in session (`Session::put('locale', ...)`) - No client-side state management (Alpine.js for minimal interactivity in navigation dropdowns) ## Key Abstractions **Controller Base Class:** - Purpose: Empty abstract class for controller type-hinting - File: `app/Http/Controllers/Controller.php` - Pattern: `abstract class Controller {}` — no shared logic **Form Request Classes:** - Purpose: Encapsulate validation and auth logic for form submissions - Files: `app/Http/Requests/ProfileUpdateRequest.php`, `app/Http/Requests/Auth/LoginRequest.php` - Pattern: Extends `FormRequest`, defines `rules()`, may include `authorize()` and custom `authenticate()` methods **Export Classes (Excel):** - Purpose: Query + format data for spreadsheet download - Files: `app/Exports/HourlyRainfallExport.php`, `app/Exports/WaterLevelExport.php` - Pattern: Implement `FromCollection`, `WithHeadings`, `ShouldAutoSize`; constructor receives filter params; `collection()` returns query result **View Components:** - Purpose: Blade component classes for layout rendering - Files: `app/View/Components/AppLayout.php`, `app/View/Components/GuestLayout.php` - Pattern: Extends `Component`, returns view in `render()` ## Entry Points **Web Application:** - Location: `routes/web.php` (main), `routes/auth.php` (auth routes, included from web.php:85) - Triggers: Browser HTTP requests - Middleware stack: `web` group → `auth` or `guest` or `admin` → controller - Custom middleware: `admin` alias → `AdminMiddleware`; `LocalizationMiddleware` appended to `web` group **API:** - Location: `routes/api.php` - Triggers: External IoT systems, mobile clients - Middleware stack: `api` group only (no auth middleware on station data endpoints) - No authentication required on station data endpoints (public JSON) **Console:** - Location: `routes/console.php` - Triggers: `php artisan inspire` (only default, no custom commands) ## Architectural Constraints - **No Eloquent ORM usage:** All data access (except User auth) uses raw SQL via `DB::table()` or `DB::select()` with named/positional bindings. This means: no model relationships, no scopes, no eager loading, no model events. - **PostgreSQL-specific SQL:** The codebase uses `TO_CHAR()`, `EXTRACT()`, `::date` casts, `::time` casts, `DATE_TRUNC()`, `INTERVAL`, `DISTINCT ON` — this is **not portable** to other databases. - **SQL injection risk from string interpolation:** Several controllers use string interpolation (`"WHERE s.stationid = '{$stationFilter}'"`) instead of parameterized bindings (see `RainfallController.php:36`, `WaterLevelController.php:30`). - **No automated tests for domain controllers:** Tests only exist for Laravel Breeze auth scaffolding (`tests/Feature/Auth/` — most files are empty). - **Service layer is thin:** Only one service class (`FcmService`) exists. All business logic lives in controllers. - **Mixed view inheritance:** `layout.app` uses `@include()` for header and `@yield()` for content sections; `layouts.app` (Laravel Breeze default) uses component-based slots (`{{ $slot }}`). These are two separate layout systems. ## Error Handling **Strategy:** Per-controller try/catch with `redirect()->back()->with('error', $message)` pattern. **Patterns:** - `AdminController::storeUser()`: Explicit `try/catch` for `ValidationException` and general `\Exception` — returns first error message to session flash - `AuthenticatedSessionController::store()`: Returns `back()->with('error', ...)` for block/failed states - `FcmService`: Throws `\Exception` if token fetch fails — caught by `AlertController` implicitly (no try/catch) - Validation on form requests: Uses `$request->validate()` inline in controllers (most cases) or dedicated `FormRequest` classes ## Cross-Cutting Concerns **Logging:** - No application-level logging detected beyond default Laravel log channel - `FIREBASE_CREDENTIALS` JSON read from file, no credential caching **Validation:** - Inline `$request->validate([...])` in most controllers (`AdminController`, `RainfallController`, `WaterLevelController`) - Form request classes for profile update (`ProfileUpdateRequest`) and login (`LoginRequest`) - No validation on API POST `/alert` endpoint (raw `$request->stationid` access without validation) **Authentication:** - Web: Session-based with Laravel Breeze scaffolding, custom `is_blocked` check and `login_attempts` tracking - API: Custom username/password verification via raw SQL + `Hash::check()` — returns JSON with `error` boolean - Admin: Middleware gate `AdminMiddleware` checks `access_level !== 1` - No token-based or OAuth API auth **Localization:** - Two locales: `en` (English) and `bm` (Bahasa Malaysia) - Language files: `lang/{en,bm}/{messages,toast,auth,validation,pagination,passwords}.php` - Route: `GET /locale/{locale}` → `LocaleController::setLocale()` — sets `App::setLocale()` and `Session::put('locale')` - Middleware: `LocalizationMiddleware` reads locale from session on every request and sets `App::setLocale()` --- *Architecture analysis: 2026-05-28*