# Purge Cache Module

## 1. Pengenalan

Module untuk **papar semua cache dalam sistem** dan **flush/rebuild kesemuanya dengan satu klik**. Direka khas untuk workflow "direct edit DB" (phpMyAdmin/tinker) — bila kau tukar data terus dalam DB tanpa melalui app Laravel, cache & mirror file masih simpan data lama. Button Flush snap semua balik mengikut DB terkini.

**URL:** `http://127.0.0.1:8000/{ADMIN_PATH}/purge-cache` — default `portal-k7m4x9/purge-cache`

**Sidebar:** Backend → Purge Cache (bawah Module Generator)

## 2. Problem Statement

App ni guna seni bina **write-through mirror**:

```
DB (source of truth)
   │
   │  admin save → BladeSyncService::syncAll()
   ▼
storage/app/private/{PHP,LAYOUTS,ENTRY,PAGE_CONTENT}/*.blade.php   ← mirror
storage/app/private/site-meta.json                                 ← meta cache
storage/app/private/{PHP,...} → dibaca PortalHandler → Blade::render() setiap request
```

Kalau kau **edit DB terus** (phpMyAdmin), `syncAll()` tak dipanggil → mirror & cache basi:
- `site-meta.json` — senarai site/page basi
- Mirror blade — frontend render content lama
- Cache table (`settings`, `backend_menu_*`, `spatie.permission.cache`) — nilai basi
- Compiled config/routes (`bootstrap/cache/*.php`) — route/config basi

**Flush All Cache menyelesaikan semua ni serentak.**

## 3. Apa Yang Dipapar (Overview Page)

| Card/Section | Sumber data | Penerangan |
|---|---|---|
| App Cache (Database) | table `cache` | Jumlah key active/expired + prefix (`cms-cache-`) |
| Compiled Views | `storage/framework/views/` | Bilangan blade compiled + saiz (KB) |
| Site Mirror Files | `storage/app/private/` | Jumlah mirror file + status `site-meta.json` |
| Stale .tmp Files | `bootstrap/cache/*.tmp` | File sisa yang akan dibuang automatik |
| App Cache Keys (table) | table `cache` | Senarai key (prefix ditanggalkan), status Active/Expired, TTL ("Never" untuk forever) |
| Compiled Framework Caches (chips) | `bootstrap/cache/` | config/events/packages/services/routes*.php — sekali lalu, badge "X/Y cached" |
| Site Mirror (table) | `storage/app/private/` | File count + last synced per folder |
| Stale .tmp (table) | `bootstrap/cache/` | Senarai file .tmp stale (merah, muncul jika ada) |

**Susun atur jadual:** Header dan kandungan semua jadual (App Cache Keys, Site Mirror, Stale .tmp) dipaparkan center (`text-center`) supaya kolum status/TTL dan angka lebih mudah dibaca.

**Laravel Log page** (`/{ADMIN_PATH}/purge-cache/logs`): paparan read-only 500 baris terakhir daripada log channel aktif, dengan butang kembali ke Purge Cache.

**Nota key (App Cache Keys):**

| Key | Dari mana | TTL | Perlu? |
|---|---|---|---|
| `backend_menu_super-admin` | `MenuHelper::getMenu()` — cache sidebar admin | 60 saat | ✅ auto-renew |
| `settings` | `SettingHelper::get()` — semua setting site | Forever | ✅ dibuang bila save setting |
| `spatie.permission.cache` | Package spatie/laravel-permission | ~23 jam | ✅ auto-renew |

## 4. Routes

| Method | URI | Middleware | Name |
|--------|-----|------------|------|
| GET | `/{ADMIN_PATH}/purge-cache` | `auth:admin` | `admin.purge-cache.index` |
| GET | `/{ADMIN_PATH}/purge-cache/logs` | `auth:admin` | `admin.purge-cache.logs` |
| POST | `/{ADMIN_PATH}/purge-cache/flush` | `auth:admin` | `admin.purge-cache.flush` |

## 5. Controller — `PurgeCacheController`

**Location:** `app/Http/Controllers/Backend/PurgeCacheController.php`

### `index()` — papar overview (read-only, tak tulis apa-apa)
- App cache: `DB::table('cache')` — strip prefix guna `preg_replace` (tolerate optional `:` separator), kira active/expired, flag `forever` jika TTL > 1 tahun
- View cache: `File::files(storage_path('framework/views'))`
- Compiled caches: existence + mtime `bootstrap/cache/{config,events,packages,services}.php` + `routes*.php`
- Stale tmp: glob `bootstrap/cache/*.tmp`
- Site mirror: mtime `site-meta.json` + file count/last sync per folder (`PHP`, `LAYOUTS`, `ENTRY`, `PAGE_CONTENT`)

### `flush()` — urutan penuh
1. `app(BladeSyncService::class)->syncAll()` — rebuild mirror DB → storage + `site-meta.json`
   - **tulis/overwrite** fail mirror untuk setiap rekod DB yang ada nilai
   - **padam** fail mirror yang nilainya dikosongkan dalam DB (page_content / layout / entry_script / component content)
   - **prune fail yatim** (`pruneOrphans()`) — fail blade yang **tiada padanan** dalam DB (page dipadam, slug ditukar, component dibuang, folder site hantu) + folder site yang tinggal kosong
2. `Cache::clear()` — kosongkan app cache (table `cache` sahaja)
3. `Artisan::call('optimize:clear')` — buang compiled config/route/view/event cache
4. `Artisan::call('optimize')` — rebuild config/route cache (pulihkan performance)
5. Delete semua `bootstrap/cache/*.tmp`
6. Flash success (termasuk bilangan `.tmp` + bilangan fail yatim dipadam) + redirect balik ke page overview

Format tarikh paparan: **`H:i - d-m-Y`** (24 jam).

## 6. View — `backend/module/cache/index.blade.php`

- Page header ikut partial `_page_header`
- 4 summary cards (App Cache / Compiled Views / Site Mirror / Stale .tmp)
- Button merah **Flush All Cache** → **Swal confirm dialog** (konvensi project) → submit POST form `#form-flush`
- Compiled Framework Caches papar sebagai **chips compact** (bukan table 4-row): dot hijau/kelabu + nama + tarikh sekali lalu
- Semua teks UI dalam **English**


## 7. Sidebar Menu — Seeder

**Location:** `database/seeders/BackendMenuSeeder.php`

```php
$purgeCache = $this->menu('Purge Cache', 'admin.purge-cache.index', 'fa-solid fa-broom',
    ['admin.purge-cache.index'], $backend->menu_id);
```

RoleMapping super-admin disisip **terus selepas Module Generator** (sort 19 vs 18) → memang render bawah Module Generator.

Apply ke DB sedia ada:
```bash
php artisan db:seed --class=BackendMenuSeeder
```
Seeder ni idempotent (`updateOrCreate`) dan auto-`Cache::forget('backend_menu_super-admin')` di akhir.

## 8. Safety — Dia Tak Merosakkan Apa-APA

| Flush BUAT | Keselamatan |
|---|---|
| Overwrite mirror file (`storage/app/private/`) | Mirror = salinan derive dari DB; DB tetap source of truth; tiada data hilang |
| Padam mirror file yang nilainya dikosongkan dalam DB | Fail itu hanya salinan; ia ditulis semula bila nilai DB diisi kembali |
| Prune fail yatim (`pruneOrphans()`) | Hanya fail `*.blade.php` yang **tiada padanan** dalam DB dipadam (page dipadam / slug ditukar / component dibuang) + folder site yang tinggal kosong |
| `Cache::clear()` | Table `cache` sahaja; **sessions TAK disentuh** (user kekal login) |
| `optimize:clear` | Laravel auto-rebuild compiled caches pada request seterusnya |
| Delete `bootstrap/cache/*.tmp` | File stale yang takde siapa guna |

| Flush TIDAK sentuh |
|---|
| Database content (articles, sliders, pages, users...) |
| Upload files (`storage/app/public/`) |
| Sessions (takde logout) |
| Code files (`app/`, `resources/views/`, `routes/`) |

**Kesan sampingan (bukan kerosakan):**
- Ambil masa ~1–2 saat (rebuild semua mirror)
- Request pertama lepas flush sikit perlahan (compiled caches bina semula)
- ⚠️ Kalau kau pernah **edit file mirror MANUAL** dalam `storage/app/private/` tanpa tukar DB → edit tu akan hilang (flush overwrite mengikut DB). Cara betul: edit melalui admin panel.

## 9. Bila Nak Guna

1. Lepas **edit DB terus** (phpMyAdmin/tinker) — tekan Flush supaya frontend reflect perubahan
2. Lepas **tukar setting** terus dalam table `settings`
3. Lepas **code/config change** — clear compiled routes/config basi
4. **Troubleshooting** — "pelik sikit behaviour ni?" → Flush (standard step)
5. Nak **bersihkan .tmp stale** dalam `bootstrap/cache/`

Flush boleh ditekan sesering mana pun — ia read-then-rebuild, bukan delete data.

## 10. FAQ / Troubleshooting

**Q: Kenapa `config.php` / `events.php` tunjuk "Not cached"?**
A: File tu hanya wujud selepas `php artisan optimize` / `config:cache` dijalankan. Bila `optimize:clear` berjalan (atau kau clear manual), ia hilang sehingga re-optimize. Tekan Flush → controller panggil `optimize` → file wujud balik. `packages.php`/`services.php` auto-regenerate oleh Laravel package discovery.

**Q: Kenapa dulu huruf key terpotong (`ackend_menu_...`, `ettings`)?**
A: Raw key dalam DB ialah `cms-cache-settings` (prefix **tanpa** `:`). Kod lama strip `strlen(prefix) + 1` huruf (anggap ada `:`) → terpotong 1 huruf ekstra. Fixed guna `preg_replace('/^prefix:?/', '')` yang tolerate kedua-dua bentuk.

**Q: TTL "9 years" untuk `settings` tu apa?**
A: Cara `Cache::rememberForever` Laravel bekerja (expiration ~10 tahun hadapan). Lepas fix, ia papar **"Never"**. Key ni dibuang automatik bila admin save setting (`SettingHelper::set()` → `Cache::forget('settings')`).

**Q: Kenapa fail mirror lama masih ada walaupun data sudah dibuang/diubah dalam DB?**
A: Sebelum ini `syncAll()` hanya **overwrite** fail yang ada padanan dalam DB — fail untuk rekod yang dikosongkan, dipadam, atau slugnya ditukar akan **tertinggal (stale)**, dan frontend tetap render versi lama (kes sebenar: `/nres/home` masih papar dokumen 204 KB walaupun `page_content` sudah dikosongkan). Sekarang `syncAll()` **padam** fail yang nilainya dikosongkan **dan** prune fail yatim, jadi tekan **Flush All Cache** sahaja sudah cukup. Mesej flash akan papar bilangan fail yatim yang dipadam.

**Q: User logged out lepas Flush?**
A: Tidak. Session guna table `sessions`, bukan table `cache`.

**Q: Permission — siapa boleh akses?**
A: Semua admin (`auth:admin`) — tiada permission gate khusus (ikut pattern profile routes). Kalau nak restricted, tambah permission `cache.purge` + `@can` pada button.

## 11. Fail Berkaitan

| Fail | Peranan |
|---|---|
| `app/Http/Controllers/Backend/PurgeCacheController.php` | Controller (index + flush) |
| `resources/views/backend/module/cache/index.blade.php` | View overview + button flush |
| `routes/web.php` | 3 route dalam group `auth:admin` |
| `database/seeders/BackendMenuSeeder.php` | Menu sidebar + role mapping |
| `app/Services/BladeSyncService.php` | Dipanggil flush() untuk rebuild mirror |
| `app/Services/FrontendSiteCacheService.php` | Rebuild `site-meta.json` (dalam syncAll) |
