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:
| Role | Access Scope |
|---|---|
| Resident | Any document where they are the requester_id |
| Official | Documents issued by their own barangay only |
| Admin | Global 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 arriveresident.{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():
- Browsershot renders the Blade PDF template via headless Chromium
- File is stored at the user-centric path
- SHA-256 checksum is recorded
- Transaction status updated to
issued DocumentIssuedNotificationdispatched 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 Key | TTL | Invalidated When |
|---|---|---|
channel_auth_{userId}_{barangayCode} | 15 min | Term ended, delegation revoked |
municipality_{id}_barangay_ids | 30 min | Barangay added/removed from municipality |
municipality_{id}_stats | 30 min | Resident transferred, barangay added |
all_barangays | Permanent | Any barangay created/deleted |
all_municipalities | Permanent | Any municipality created/deleted |
| Spatie permissions | — | Any 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