← Back to Project|Technical Documentation
Public · Read-only

Multi-tenancy, data flow, real-time, and infrastructure design

System Architecture

High-Level Overview

The Barangay Module is a vertical extension of the existing Bataeño Pass ecosystem. Core identity services (registration, eGovPH sync) are handled by the core platform. The module adds isolated logic for:

  • Barangay-level document processing
  • Household and residency management
  • Official term and delegation tracking
┌─────────────────────────────────────────────────────────┐
│                  Bataeño Pass Core                       │
│         eGovPH SSO · Identity · National ID              │
└──────────────────────┬──────────────────────────────────┘
                       │ OAuth 2.0 trust
┌──────────────────────▼──────────────────────────────────┐
│               Barangay Module (New)                      │
│                                                          │
│  ┌─────────────────┐  ┌──────────────────────────────┐  │
│  │  Resident Portal │  │     Official Filament Panel  │  │
│  │  (Livewire)      │  │     (per-barangay tenant)    │  │
│  └─────────────────┘  └──────────────────────────────┘  │
│                                                          │
│  ┌──────────────────────────────────────────────────┐   │
│  │       City Admin Panel (/city-admin)              │   │
│  │       (per-municipality tenant, read-only)        │   │
│  └──────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

Multi-Tenancy Design

The system uses Filament's multi-tenancy with two tenant scopes:

  • Official Panel → scoped to barangay_id (one panel per barangay)
  • City Admin Panel → scoped to municity_code (one panel per municipality)

The User model implements canAccessPanel(), getTenants(), and canAccessTenant(). Super Admin bypasses the Gate::before tenancy guard — a critical fix discovered in Week 6 where the guard was blocking cross-panel Super Admin access.


Document Storage — User-Centric Path

Documents use a user-centric storage hierarchy instead of barangay-centric. This ensures residents retain access to historical documents even after relocating to a different barangay:

storage/app/generated/{user_id}/{barangay_code}/{doc_slug}/{transaction_id}_signed.pdf

Access control levels:

RoleAccess Scope
ResidentAny document where they are the requester_id
OfficialDocuments issued by their own barangay only
AdminGlobal download access for audit and support

Real-Time Broadcasting

Laravel Reverb serves as the WebSocket server. Private channels are configured for two audiences:

  • barangay.{id} — officials receive live notifications when new document requests arrive
  • resident.{id} — residents receive status updates when documents are issued or rejected

Channel Authorization Caching

To prevent repeated database hits on every WebSocket reconnect (identified via Telescope as 500–1000ms each), channel authorization is cached:

Cache::remember("channel_auth_{$userId}_{$barangayCode}", 900, fn() => ...);

TTL: 15 minutes. Must be invalidated when a BarangayTerm is ended, reassigned, or when a delegation is revoked.


Queue Jobs for Document Processing

Document processing is fully asynchronous via Laravel Queue (database driver):

ProcessDocumentRequest

Handles official notifications, resident confirmation email, and DocumentRequestCreated broadcast. Dispatched via ->afterCommit() to prevent race conditions where the job fires before the DB transaction completes.

ProcessDocumentApproval

Wraps DocumentApprovalService::generateAndSign():

  1. Browsershot renders the Blade PDF template via headless Chromium
  2. File is stored at the user-centric path
  3. SHA-256 checksum is recorded
  4. Transaction status updated to issued
  5. DocumentIssuedNotification dispatched to resident

Configuration: 120-second timeout (Browsershot can take 2–10 seconds), 3 retries with 10-second backoff. On final failure, reverts transaction status back to pending so the official can retry manually.


Caching Strategy

Identified via Laravel Telescope in Week 6 as a performance bottleneck.

Cache KeyTTLInvalidated When
channel_auth_{userId}_{barangayCode}15 minTerm ended, delegation revoked
municipality_{id}_barangay_ids30 minBarangay added/removed from municipality
municipality_{id}_stats30 minResident transferred, barangay added
all_barangaysPermanentAny barangay created/deleted
all_municipalitiesPermanentAny municipality created/deleted
Spatie permissionsAny role assignment/revocation

Data Flow Diagrams

Online Document Request

Resident
  → Logs in via eGovPH OAuth
  → Selects document type (Livewire form)
  → Dynamic requirements rendered per type
  → Submits form
  → DocumentTransaction created (status: pending)
  → ProcessDocumentRequest job dispatched (afterCommit)
  → DocumentRequestCreated broadcast → official channel
  → Official notified in Filament panel

Official
  → Reviews request queue
  → GovernanceService validates signing authority
  → Approves
  → ProcessDocumentApproval job dispatched
  → Browsershot generates PDF from Blade template
  → File stored, checksum recorded, download token issued
  → Transaction status → "issued"
  → Resident notified
  → Resident downloads via one-time token URL

Walk-In Document Request

Resident arrives at barangay desk

Option A — NFC Tap:
  → Official taps resident's Bataeño Pass card
  → UID Bridge resolves UID to local resident record
  → If not found: register resident first
  → Walk-in form pre-filled

Option B — Manual Lookup:
  → Staff searches via 2-column Name + Birthday form
  → Duplicate detection on UI + server side

→ Official selects document type + enters required fields
→ DocumentTransaction created
  (requester_id = null, request_origin = "walk-in")
→ Same approval pipeline as online flow

Residency Request

Resident → Household Dashboard

New Residence:
  → Fills address form (unit, street, subdivision, ownership)
  → ResidencyRequest created (household_id = null)
  → Official reviews + approves
  → New House + Household auto-created
  → HouseholdMemberProfile created (role: Head)
  → Resident's barangay_id synced

Join Existing:
  → Resident finds household head (debounced search)
  → ResidencyRequest sent to head for approval
  → HouseholdMemberProfile created
  → Address inherited from household's house record
  → Resident's barangay_id synced if different