# External Integrations **Analysis Date:** 2026-02-13 ## APIs & External Services **Next.js Frontend Site Revalidation:** - Service: Custom webhook to Next.js `/api/revalidate` - What it's used for: Trigger static site regeneration when articles, pages, or documents change - SDK/Client: Guzzle HTTP client (`guzzlehttp/guzzle`) - Implementation: `app/Services/SiteRevalidationService.php` - Methods: - `SiteRevalidationService::revalidateArticle(?slug)` - Trigger article cache invalidation - `SiteRevalidatService::revalidatePage(?slug)` - Trigger page cache invalidation - `SiteRevalidationService::revalidateDocument(?slug)` - Trigger document cache invalidation - Triggered by: `app/Observers/DocumentObserver.php` on article/page create/update/delete - Timeout: 5 seconds - Error handling: Logged as warning, does not fail request **Email Service Provider (Configurable):** - Services supported: SMTP, Mailgun, Postmark, AWS SES - SDK/Client: Laravel Mail facades - Implementation: Controllers use `Mail::to($email)->queue(MailClass::class)` - Auth: `MAIL_HOST`, `MAIL_PORT`, `MAIL_USERNAME`, `MAIL_PASSWORD`, or API keys - Dev/Test: Mailpit on `localhost:1025` (configured in `.env.example`) - Queue integration: All mail uses async queue for better performance ## Data Storage **Primary Database:** - Type/Provider: MySQL (production) or SQLite (development) - Connection config: `config/database.php` - Connection env vars: - `DB_CONNECTION` - Type: `mysql` or `sqlite` - `DB_HOST` - Database server (e.g., `127.0.0.1`) - `DB_PORT` - Port (default 3306 for MySQL) - `DB_DATABASE` - Database name - `DB_USERNAME` - Username - `DB_PASSWORD` - Password - ORM/Client: Laravel Eloquent (built-in) - Encryption: AES-256-CBC via `config/app.php` cipher - Notable tables: - `users` - System users (staff, admin) - `members` - Member profiles (encrypted national IDs) - `finance_documents` - Finance approvals with multi-tier workflows - `payments` - Member fee payments - `articles`, `pages`, `categories`, `tags` - CMS content - `documents` - Document library entries - `roles`, `permissions`, `model_has_roles` - Spatie permission tables - `settings` - System-wide configuration (cached) **File Storage:** - Databases: Local disk and S3 configurable - Private files: `storage/app/` (served via controller) - Public files: `storage/app/public/` (served via `/storage/...` URL) - S3 Config: `config/filesystems.php` → `disks.s3` - S3 env vars: - `AWS_ACCESS_KEY_ID` - `AWS_SECRET_ACCESS_KEY` - `AWS_DEFAULT_REGION` - `AWS_BUCKET` - `AWS_URL` (optional CDN) - `AWS_ENDPOINT` (optional for S3-compatible services) **Caching:** - Providers: File (default), Redis, Memcached, DynamoDB - Config: `config/cache.php` - Default driver: `file` (suitable for single-server, can switch to Redis) - Used for: `settings()` helper caching, query result caching - Cache prefix: `laravel_cache_` (configurable via `CACHE_PREFIX` env) - Settings cache: Automatically cleared on `SystemSetting` model mutation **Session Storage:** - Providers: File (default), Database, Redis - Config: `config/session.php` - Default driver: `file` - Lifetime: 120 minutes (configurable via `SESSION_LIFETIME` env) - CSRF tokens: Protected via Laravel middleware ## Authentication & Identity **Auth Provider:** - Type: Custom (Session-based) - Implementation: Built-in Laravel authentication with `User` model - Guard: `web` (session-based) - Config: `config/auth.php` - Provider: Eloquent user provider (`App\Models\User`) **API Token Authentication:** - Service: Laravel Sanctum - Implementation: `config/sanctum.php` - For: Public API endpoints (`/api/v1/*`) - Stateful domains: `localhost`, `127.0.0.1`, custom domain (via `SANCTUM_STATEFUL_DOMAINS` env) - Token prefix: Customizable via `SANCTUM_TOKEN_PREFIX` env - Expiration: No default expiration (tokens live indefinitely unless custom set) **Role-Based Access Control (RBAC):** - Service: Spatie Laravel Permission - Package: `spatie/laravel-permission@^6.23` - Config: `config/permission.php` - Models: `Spatie\Permission\Models\Role`, `Spatie\Permission\Models\Permission` - Tables: - `roles` - Available roles - `permissions` - Available permissions - `model_has_roles` - User-to-role assignments - `model_has_permissions` - User-to-permission direct assignments - `role_has_permissions` - Role-to-permission assignments - Core roles (seeded in `database/seeders/`): - `admin` - Full system access - `finance_requester` - Submit finance documents - `finance_cashier` - Tier 1 approvals (small amounts) - `finance_accountant` - Tier 2 approvals (medium amounts) and ledger recording - `finance_chair` - Tier 2 approvals (medium amounts) - `finance_board_member` - Tier 3 approvals (large amounts) - `secretary_general` - CMS management, member management - `membership_manager` - Member lifecycle management **Identity/National ID Encryption:** - Encryption: AES-256 via Laravel's encryption - Fields in `members` table: - `national_id_encrypted` - Stores encrypted national ID - `national_id_hash` - SHA256 hash for searching (indexed) - `national_id` - Virtual accessor (decrypts on read) - Location: `app/Models/Member.php` ## Webhooks & Callbacks **Incoming Webhooks:** - Site revalidation endpoint: `/api/revalidate` (public, requires valid token) - Token: `NEXTJS_REVALIDATE_TOKEN` env var - Method: `POST` - Payload: `{ type: 'article'|'page'|'document', slug?: string }` **Outgoing Webhooks:** - Next.js site revalidation: `POST {NEXTJS_REVALIDATE_URL}` - URL: `NEXTJS_REVALIDATE_TOKEN` env var - Token header: `x-revalidate-token` - Payload: `{ type: 'article'|'page'|'document', slug?: string }` - Triggered on: Article/Page/Document create/update/delete - Implementation: `app/Services/SiteRevalidationService.php` - Timeout: 5 seconds, failures logged but do not block request ## Monitoring & Observability **Error Tracking:** - Service: Spatie Ignition (error page companion) - Package: `spatie/laravel-ignition@^2.0` - Used in: Development and error page debugging - Config: `config/app.php` → providers **Logging:** - Framework: Monolog (via Laravel) - Config: `config/logging.php` - Default channel: `stack` (multi-handler) - Channels available: - `single` - Single log file: `storage/logs/laravel.log` - `daily` - Rotate daily, keep 14 days - `slack` - Send logs to Slack webhook (env: `LOG_SLACK_WEBHOOK_URL`) - `papertrail` - Syslog UDP to Papertrail (env: `PAPERTRAIL_URL`, `PAPERTRAIL_PORT`) - `stderr` - Output to stderr - `syslog` - System syslog - `errorlog` - PHP error_log - `log` - File channel with custom naming - `array` - In-memory (testing only) - Log level: `debug` (default, configurable via `LOG_LEVEL` env) - Deprecations channel: Null by default (can redirect via `LOG_DEPRECATIONS_CHANNEL` env) **Audit Logging:** - Service: Custom audit logger - Implementation: `app/Support/AuditLogger.php` - Usage: `AuditLogger::log($action, $auditable, $metadata)` - Tracked: Model mutations (create/update/delete) for compliance - Storage: Appended to audit log entries ## CI/CD & Deployment **Hosting:** - Platform: Dedicated servers, Docker (Laravel Sail), or cloud (AWS, etc.) - Port: API typically runs on port 8001 (not 8000 - that's often occupied) **Build Process:** - Frontend assets: `npm run build` (Vite compilation) - Backend: `composer install` (or `composer install --no-dev` for production) - Migrations: `php artisan migrate --force` - Cache: `php artisan config:cache`, `php artisan view:cache` **Code Quality (Pre-commit):** - Linter: Laravel Pint (PSR-12) - Run: `./vendor/bin/pint` - Fixes style issues automatically - Static analysis: PHPStan - Run: `./vendor/bin/phpstan analyse` - Config: `phpstan.neon` (if present) **Testing:** - Unit & Feature Tests: PHPUnit - Run: `php artisan test` - Run specific: `php artisan test --filter=ClassName` - Config: `phpunit.xml` - Browser/E2E Tests: Laravel Dusk - Run: `php artisan dusk` - Uses Chrome/Chromium driver ## Environment Configuration **Required Environment Variables:** Core: - `APP_NAME` - Application name - `APP_ENV` - Environment: `local`, `production` - `APP_DEBUG` - Boolean: enable/disable debug mode - `APP_KEY` - Base64 encryption key (auto-generated) - `APP_URL` - Full URL: `https://member.usher.org.tw` (production) Database: - `DB_CONNECTION` - `mysql` (default) or `sqlite` - `DB_HOST` - Database server - `DB_PORT` - Port (3306 for MySQL) - `DB_DATABASE` - Database name - `DB_USERNAME` - Username - `DB_PASSWORD` - Password Mail: - `MAIL_MAILER` - `smtp` (default), `mailgun`, `postmark`, `ses`, `log` - `MAIL_HOST` - SMTP host - `MAIL_PORT` - SMTP port (587 typical) - `MAIL_USERNAME` - SMTP username - `MAIL_PASSWORD` - SMTP password - `MAIL_ENCRYPTION` - `tls`, `ssl`, or null - `MAIL_FROM_ADDRESS` - From email address - `MAIL_FROM_NAME` - From name Caching & Queuing: - `CACHE_DRIVER` - `file` (default), `redis`, `memcached`, `dynamodb` - `QUEUE_CONNECTION` - `sync` (default), `database`, `redis`, `sqs` - `SESSION_DRIVER` - `file` (default), `database`, `redis` Feature Toggles: - `REGISTRATION_ENABLED` - Boolean: allow public registration (`false` by default) Frontend Integration: - `NEXTJS_REVALIDATE_URL` - Full URL to Next.js revalidation endpoint - `NEXTJS_REVALIDATE_TOKEN` - Bearer token for revalidation requests - `NEXTJS_PUBLIC_PATH` - Optional: path to Next.js `public/` directory (for local asset sync) **Secrets Location:** - Method: Environment variables (`.env` file, never committed) - Production: Use hosted secrets manager or CI/CD environment variables - Never commit: Passwords, API keys, encryption keys, tokens ## Optional: Redis **If using Redis for caching/sessions/queue:** - Config: `config/database.php` → `redis` - Env vars: - `REDIS_HOST` - `127.0.0.1` (default) - `REDIS_PORT` - `6379` (default) - `REDIS_PASSWORD` - Password (null by default) - `REDIS_CLIENT` - `phpredis` (default) - `REDIS_CLUSTER` - `redis` (default) - Databases: - Default: 0 (primary connection) - Cache: 1 (separate database for cache keys) ## Optional: AWS Integration **If using AWS S3 for file storage:** - Config: `config/filesystems.php` → `disks.s3` - Env vars: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_DEFAULT_REGION`, `AWS_BUCKET` **If using AWS SES for email:** - Config: `config/mail.php` → `mailers.ses` - Env vars: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_DEFAULT_REGION` **If using AWS SQS for queue:** - Config: `config/queue.php` → `connections.sqs` - Env vars: AWS credentials + `SQS_PREFIX`, `SQS_QUEUE`, `SQS_SUFFIX` --- *Integration audit: 2026-02-13*