# Codebase Documentation — CMS Laravel v1

> Comprehensive documentation for the CMS Laravel v1 codebase.
> Built on Laravel 13 with dual authentication, multi-lingual content modules, dynamic menu system, and a component-based CMS builder.

---

## Table of Contents

1. [Project Overview](#1-project-overview)
2. [Architecture Summary](#2-architecture-summary)
3. [Authentication System](#3-authentication-system)
4. [Content Modules](#4-content-modules)
5. [CMS Builder (FrontendSite)](#5-cms-builder-frontendsite)
6. [Menu System](#6-menu-system)
7. [Services](#7-services)
8. [View Composers](#8-view-composers)
9. [Database Schema Overview](#9-database-schema-overview)
10. [Routes](#10-routes)
11. [Permissions & Roles](#11-permissions--roles)
12. [Frontend Rendering Pipeline](#12-frontend-rendering-pipeline)
13. [Helpers & Utilities](#13-helpers--utilities)
14. [Directory Structure Reference](#14-directory-structure-reference)

---

## 1. Project Overview

### Tech Stack
- **Framework:** Laravel 13
- **PHP:** ^8.2
- **Database:** MySQL (Laravel migration-based)
- **Frontend:** Blade templating, TailAdmin theme, Vite bundler
- **Key Packages:**
  - `spatie/laravel-permission` — Roles & permissions (admin + user guards)
  - `spatie/laravel-activitylog` — Activity logging with custom observer
  - `php-flasher/flasher-laravel` — Flash notifications
  - `barryvdh/laravel-elfinder` — File manager integration

### Purpose
A dual-auth CMS with admin panel (`/admin`) and frontend user panel (`/user`), featuring:
- 10 content modules with multi-language support
- Component-based frontend site builder (CMS Builder)
- Dynamic role-based menu systems (backend sidebar + frontend navigation)
- Visitor tracking, activity logging, route management
- Auto-permission generation from controller scanning

---

## 2. Architecture Summary

```
Request Flow (High Level):
┌─────────────┐     ┌─────────────────┐     ┌──────────────────┐
│  Public      │────▶│  FrontendSite   │────▶│  PortalHandler   │
│  /slug/...  │     │  RenderController│     │  (Render Engine) │
└─────────────┘     └─────────────────┘     └──────────────────┘
                                                    │
                          ┌─────────────────────────┼──────────────────┐
                          │                         │                  │
                          ▼                         ▼                  ▼
                   ┌──────────────┐       ┌──────────────┐     ┌──────────────┐
                   │  Site Layout │       │  Page Layout │     │  Components  │
                   │  (HTML shell)│       │  (component  │     │  (Blade PHP) │
                   └──────────────┘       │   ordering)  │     └──────────────┘
                                          └──────────────┘

┌─────────────┐     ┌─────────────────┐
│  Admin      │────▶│  CRUD Controllers│────▶ DB Models
│  /admin/... │     │  + Form Requests │
└─────────────┘     └─────────────────┘
```

### Key Design Patterns

| Pattern | Where Used |
|---------|------------|
| **Service Layer** | `PortalHandler`, `BladeSyncService`, `MenuFilterService`, `ControllerScanner`, `RouteScanner` |
| **View Composers** | `PublicMenuComposer`, `FrontendUserMenuComposer`, `MenuComposer` (legacy) |
| **Form Requests** | Each content module + user/ref management has a dedicated request class |
| **Repository-less** | Controllers query Eloquent models directly (no repository pattern) |
| **Polymorphic Relations** | Visits (visitable + visitor), Activity Log (subject + causer) |
| **Multi-Language** | Separate translation tables (5 modules) + self-referencing (3 modules) |
| **Blade Storage Sync** | DB-stored templates are synced to `storage/app/` as `.blade.php` files for `Blade::render()` |

---

## 3. Authentication System

### 3.1 Dual Guard Architecture

| Guard | Model | Panel URL | Routes Prefix |
|-------|-------|-----------|---------------|
| `admin` | `BackendUser` | `/admin` | `admin.*` |
| `user` | `FrontendUser` | `/user` | `user.*` |

Both use:
- Spatie `HasRoles` trait (roles/permissions per guard)
- Email verification (`MustVerifyEmail`)
- Two-factor authentication (expires_at + code)
- Password reset via email
- Activity logging via `LogsActivity` trait

### 3.2 BackendUser (`app/Models/Backend/BackendUser.php`)
- Table: `backend_users`
- Guard: `admin`
- Key fields: `username`, `email`, `is_active`, `expires_at`, `two_factor_code`, `last_login_at`
- Traits: `HasFactory`, `Notifiable`, `LogsActivity`, `HasRoles`

### 3.3 FrontendUser (`app/Models/Backend/FrontendUser.php`)
- Table: `frontend_users`
- Guard: `user`
- Same structure as BackendUser (identical schema)
- Traits: `HasFactory`, `Notifiable`, `LogsActivity`, `HasRoles`

### 3.4 Auth Controllers

| Guard | Login | Register | Password Reset | Email Verify |
|-------|-------|----------|----------------|--------------|
| Admin | `Backend/Auth/AuthenticatedSessionController` | — (admin created via seeder) | Via same controller | `Backend/Auth/EmailVerificationController` |
| User | `Frontend/Auth/LoginController` | `Frontend/Auth/RegisterController` | Via LoginController | `Frontend/Auth/VerifyEmailController` |

> **Detailed CMS Auth System:** Rujuk [CMS-AUTH-SYSTEM.md](./CMS-AUTH-SYSTEM.md) untuk dokumentasi lengkap tentang CMS-based auth pages, route redirects, notification overrides, dan settings.

### 3.5 Middleware

| Middleware | Guard | Purpose |
|------------|-------|---------|
| `AdminMiddleware` | `admin` | Redirects unauthenticated admin to `/admin/login` |
| `UserMiddleware` | `user` | Redirects unauthenticated users to `/user/login` |
| `Authenticate` | configurable | Base authentication middleware |
| `VisitorLogMiddleware` | — | Logs visits for every incoming request (frontend only) |

---

## 4. Content Modules

### 4.1 Module Matrix

| # | Module | Model | Table | PK | Translation Pattern | Routes Prefix |
|---|--------|-------|-------|-----|-------------------|---------------|
| 1 | **Article** | `ContentArticle` | `content_article` | `article_id` | Separate table | `content-article.*` |
| 2 | **Application** | `ContentApplication` | `content_applications` | `application_id` | Separate table | `content-application.*` |
| 3 | **Download** | `ContentDownload` | `content_downloads` | `download_id` | Self-referencing | `content-download.*` |
| 4 | **Slider** | `ContentSlider` | `content_slider` | `slider_id` | Separate table | `content-slider.*` |
| 5 | **Image** | `ContentImage` | `content_images` | `image_id` | Self-referencing | `content-image.*` |
| 6 | **Video** | `ContentVideo` | `content_video` | `video_id` | Separate table | `content-video.*` |
| 7 | **Photo Gallery** | `ContentPhotoGallery` | `content_photo_gallery` | `gallery_id` | Separate table | `content-photo-gallery.*` |
| 8 | **Photo List** | `ContentPhotoList` | `content_photo_list` | `photo_id` | Self-referencing | `content-photo-list.*` |
| 9 | **Public Holiday** | `ContentPublicHoliday` | `content_public_holidays` | `public_holiday_id` | Flat (no translations) | `content-public-holiday.*` |
| 10 | **Calendar** | `ContentCalendar` | `content_calendar` | `calendar_id` | Separate table | `content-calendar.*` |

### 4.2 Translation Patterns

#### Pattern A: Separate Translation Table (recommended)
Used by: Article, Application, Slider, Video, Photo Gallery, Calendar

```
content_article (parent)
├── article_id (PK)
├── article_code
├── article_status
└── ...

content_article_translation (child, FK → content_article.article_id)
├── article_translation_id (PK)
├── article_translation_parent_id (FK)
├── article_translation_title
├── article_translation_content
├── article_translation_main (1/0 — is main language?)
├── article_translation_language (e.g. 'EN', 'MS')
└── ...
```

Key relationship:
```php
// Parent
public function translations(): HasMany
{
    return $this->hasMany(ContentArticleTranslation::class, 'article_translation_parent_id');
}

// Child
public function parent(): BelongsTo
{
    return $this->belongsTo(ContentArticle::class, 'article_translation_parent_id');
}
```

#### Pattern B: Self-Referencing (single table)
Used by: Download, Image, Photo List

```php
// Same table for parent + translations, linked by {entity}_parent_id
// download_parent_id = NULL → parent record
// download_parent_id = {PK} → translation of that parent

public function translations(): HasMany
{
    return $this->hasMany(self::class, 'download_parent_id', 'download_id');
}
```

### 4.3 Common Module Features

| Feature | Implementation |
|---------|---------------|
| **Status** | `ACTIVE` / `INACTIVE` (stored in `Ref` table with `_{MODULE}_STATUS` category) |
| **Sort/Reorder** | Drag-to-reorder via SortableJS + POST to `{module}.reorder` |
| **CRUD** | Standard `index`, `create`, `store`, `edit`, `update`, `destroy` |
| **Form Request** | Each module has dedicated `*Request.php` with validation rules |
| **Image Upload** | FilePond JS library, stored in `storage/app/public/{module}/` |
| **Portal Display** | Dropdown populated from `FrontendSite::where('status', true)` |

### 4.4 Content Module Detail Page Flow

Semua content module (Article, Video, Gallery) detail dimuat melalui **page component** guna query param `?id=` — bukan auto-render URL path:

```
URL: /{siteSlug}/{pageSlug}?id={code}
```
- `pageSlug` = page biasa dalam CMS Builder
- `?id={code}` = query param untuk filter single item dalam component

**Component detail pattern** (letak dalam component content type PHP):
```blade
@php
$id = request()->query('id');
$items = \App\Models\Backend\ContentArticle::with('translations')
    ->where('article_status', 'ACTIVE')->where('article_code', $id)->get();
@endphp
```

### 4.5 Modules Without Detail Pages

Slider, Download, Image, Application, Photo List only appear via `mod_*` components (list display within a page). They have **no** dedicated detail page.

---

## 5. CMS Builder (FrontendSite)

### 5.1 Concept

A component-based dynamic website builder. Everything stored in DB as Blade templates, synced to storage for efficient rendering.

### 5.2 Models & Relationships

```
FrontendSite
├── id, name, slug, layout, header_fk, footer_fk, status, is_default, sort_order
├── belongsTo(header: FrontendComponent)
├── belongsTo(footer: FrontendComponent)
├── hasMany(FrontendPage)
└── hasOne(defaultPage: FrontendPage)

FrontendPage
├── id, site_fk, name, slug, layout, content, is_default, status, sort_order
├── belongsTo(FrontendSite)
└── belongsToMany(FrontendComponent) via frontend_page_components (pivot: sort, params)

FrontendComponent
├── id, name, code (unique), content (Blade), type (PHP|HTML), category, status
└── belongsToMany(FrontendPage) via frontend_page_components

FrontendPageComponent (pivot)
├── page_fk, component_fk, sort, params
```

### 5.3 Rendering Pipeline (`PortalHandler::handle()`)

```
1. Get site meta from cache (FrontendSiteCacheService)
2. Load FrontendSite + FrontendPage (default or by slug)
3. Build menu tree from RoleMapping (role_code = 'public-user')
4. Render entry script (PHP pre-processing in page.content) → Blade::render()
5. Render each page component → Blade::render() → stored as $vars['{code}']
6. Render page layout (template ordering components)
7. Render header (navbar component) via PortalHandler variables
8. Render footer component via PortalHandler variables
9. Render site layout (full HTML shell: header + page + footer) → FINAL HTML
```

### 5.4 Blade Sync Service

`BladeSyncService::syncAll()` copies DB content to storage:

| Source | Storage Path |
|--------|--------------|
| `FrontendComponent.content` | `storage/app/PHP/{code}.blade.php` |
| `FrontendPage.layout` | `storage/app/LAYOUTS/PAGE/{siteSlug}/{pageSlug}.blade.php` |
| `FrontendPage.content` | `storage/app/ENTRY/PAGE/{siteSlug}/{pageSlug}.blade.php` |
| `FrontendSite.layout` | `storage/app/LAYOUTS/SITE/{slug}.blade.php` |

### 5.5 FrontendSiteCacheService

- Builds `site-meta.json` in storage containing all active sites, their pages, and component codes
- Rebuilds automatically if cache file is missing
- Provides `get(slug)`, `getDefault()`, `all()` methods

### 5.6 Variables Available in Components

| Variable | Source | Description |
|----------|--------|-------------|
| `$siteSlug` | PortalHandler | Current site slug |
| `$siteName` | PortalHandler | Current site name |
| `$sitePages` | PortalHandler | All pages in current site |
| `$menus` | PortalHandler | Flat collection of menu items |
| `$menuTree` | PortalHandler | Nested parent-child menu tree |
| `$_shared` | PortalHandler | `stdClass` object for sharing data between entry script → components → layout (pass by reference) |
| `$__entry__` | Page layout | Rendered output from entry script |
| `$__header__` | Page layout only | Rendered header HTML |
| `$__page__` | Site layout only | Rendered page HTML |
| `$__footer__` | Site layout only | Rendered footer HTML |
| `$errors` | PortalHandler | `ViewErrorBag` for validation error display in CMS page layouts |
| `$_{component_code}` | Page layout | Each rendered component |

### 5.7 Demo Sites (from FrontendSiteDemoSeeder)

| Site | Slug | Type | Pages | Purpose |
|------|------|------|-------|---------|
| My Modern Site | `my-site` | Static | Home, About, Contact | Landing page demo (hero, features, content) |
| Module Content Site | `module-site` | Dynamic | Home (8 modules) | Content module list demo |

---

## 6. Menu System

### 6.1 Three Menu Sub-Systems

| System | Model | Table | Purpose | Used By |
|--------|-------|-------|---------|---------|
| **Backend Menu** | `App\Models\Backend\menu\backend\Menu` | `backend_menu` | Admin sidebar navigation | `MenuHelper`, `MenuController` |
| **Frontend Menu** | `App\Models\Backend\menu\frontend\Menu` | `frontend_menu` | Public site navigation | `FrontendMenuController`, `PortalHandler` |
| **Menu Category** | `App\Models\Backend\menu\frontend\MenuCategory` | `frontend_menu_category` | Category labels for frontend menus | Assignment UI grouping |

### 6.2 Role Mapping Pattern (both backend & frontend)

```
RoleMapping (pivot model)
├── role_code (string — Spatie role name)
├── menu_id (FK to menu table)
├── parent_id (self-referencing for hierarchy, 0 = root)
├── sort (ordering)
├── status (active/inactive)
└── (frontend only): category_id, menu_group, page_id, site_id
```

### 6.3 Frontend Menu — Additional Fields

| Field | Purpose |
|-------|---------|
| `category_id` | FK to `frontend_menu_category` — defines where menu appears (main-navbar, mobile-navbar, sidebar, footer) |
| `menu_group` | Sub-group within category |
| `page_id` | Filter by page (0 = all pages) |
| `site_id` | Filter by site (0 = all sites) |

### 6.4 How Menus Reach Frontend

**PortalHandler** (for CMS Builder sites):
```php
// Queries RoleMapping with role_code = 'public-user', site_id = current site
// Builds nested tree via parent_id → menu_id recursion
// Passes $menus (flat) and $menuTree (nested) to all components
```

**PublicMenuComposer** (for legacy landing page):
```php
// Queries RoleMapping with role_code = 'public', category_id = main-navbar category
// Falls back to Menu model + hardcoded login links if no assignments exist
```

**FrontendUserMenuComposer** (for user dashboard sidebar):
```php
// Gets user roles from Spatie
// Queries RoleMapping for matching role_code
// Filters by category_id = sidebar category
```

---

## 7. Services

| Service | File | Purpose | Key Methods |
|---------|------|---------|-------------|
| **PortalHandler** | `app/Services/PortalHandler.php` | Core frontend rendering engine | `handle()` |
| **BladeSyncService** | `app/Services/BladeSyncService.php` | Sync DB Blade templates to storage | `syncAll()`, `syncComponent()`, `syncPage()`, `syncSite()`, `syncEntryPoint()` |
| **FrontendSiteCacheService** | `app/Services/FrontendSiteCacheService.php` | Build & serve site meta cache | `get(slug)`, `getDefault()`, `all()`, `rebuild()` |
| **MenuFilterService** | `app/Services/MenuFilterService.php` | Filter & redirect for menu assignment UI | `filter()`, `cleanParam()`, `redirectAfterSave()`, `buildTabLink()` |
| **ControllerScanner** | `app/Services/ControllerScanner.php` | Scan controllers for auto-permission | `scan()`, cached 1 day |
| **RouteScanner** | `app/Services/RouteScanner.php` | Scan registered routes for route manager | `scan()`, cached 1 day |

### 7.1 PortalHandler Detail

**Namespace:** `App\Services\PortalHandler`

**Dependencies:** `FrontendSiteCacheService`, `BladeSyncService`

**Methods:**

| Method | Parameters | Returns | Description |
|--------|-----------|---------|-------------|
| `handle` | `string $siteSlug, ?string $pageSlug = null` | `string` (HTML) | Renders a full site page (site layout + header + content + footer) |

> **Note:** Content module detail (Article, Video, Gallery) no longer uses separate handler methods. All detail is rendered via page component using `request()->query('id')` — see `docs/FRONTEND-COMPONENT-GUIDE.md` §4.2.

**Rendering flow for `handle()`:**
```
loadSiteMeta → getSite → getPage → buildMenuTree → renderEntryScript
→ renderComponents → renderPageLayout → renderHeader → renderFooter
→ renderSiteLayout → return HTML
```

**Key features added for CMS auth support:**

| Feature | Description |
|---------|-------------|
| `$_shared` object | `stdClass` instantiated and passed as `$vars['_shared']` — shared across entry script, components, page content, and layout (pass by reference) |
| `$errors` injection | `ViewErrorBag` from `session('errors')` merged into `$layoutVars` so `@error`, `$errors->any()`, `$errors->first()` work in CMS page layouts |
| `HttpResponseException` catch | Caught before `\Throwable` in the render pipeline — allows `throw new HttpResponseException(redirect()->to(...))` from entry scripts and layouts for proper redirects |

### 7.2 BladeSyncService Detail

**Namespace:** `App\Services\BladeSyncService`

Called automatically after create/update of:
- `FrontendComponent` → re-syncs that component
- `FrontendPage` → re-syncs page layout + entry point
- `FrontendSite` → re-syncs site layout + all its pages

---

## 8. View Composers

| Composer | File | Registered For | Purpose |
|----------|------|----------------|---------|
| **PublicMenuComposer** | `app/View/Composers/PublicMenuComposer.php` | `home.layouts.master` | Builds navbar for public landing page (legacy) |
| **FrontendUserMenuComposer** | `app/View/Composers/FrontendUserMenuComposer.php` | `frontend-user.layouts.partials.sidebar` | Builds sidebar for authenticated frontend users |
| **MenuComposer** | `app/View/Composers/MenuComposer.php` | (legacy, likely unused) | Legacy menu builder with hardcoded links |

### Registration (in `AppServiceProvider`)

```php
// Public landing page navbar
View::composer('home.layouts.master', PublicMenuComposer::class);

// Frontend user dashboard sidebar
View::composer('frontend-user.layouts.partials.sidebar', FrontendUserMenuComposer::class);
```

---

## 9. Database Schema Overview

### 9.1 Total Tables: 46

| Category | Tables | Count |
|----------|--------|-------|
| Laravel Core | `users`, `password_reset_tokens`, `sessions`, `cache`, `cache_locks`, `jobs`, `job_batches`, `failed_jobs` | 8 |
| Auth | `backend_users`, `frontend_users` | 2 |
| Spatie Permissions | `permissions`, `roles`, `model_has_permissions`, `model_has_roles`, `role_has_permissions` | 5 |
| Menu | `backend_menu`, `backend_role_mapping`, `frontend_menu`, `frontend_role_mapping`, `frontend_menu_category` | 5 |
| Content (10 modules) | 10 main + 6 translation = 16 tables | 16 |
| CMS Builder | `frontend_components`, `frontend_sites`, `frontend_pages`, `frontend_page_components` | 4 |
| System | `ref`, `routes`, `activity_log`, `visits`, `visitor_settings`, `data_dashboard` | 6 |

### 9.2 Key Patterns

- **All tables**: Auto-increment integer primary keys (no UUIDs)
- **Translation**: 6 modules use separate translation table, 3 use self-referencing, 1 has no translations
- **Polymorphic**: Visits (visitable + visitor), Activity Log (subject + causer), Spatie permissions
- **Soft Deletes**: `routes` (Laravel trait), `ref` (manual integer column)
- **Pivot Tables**: `frontend_page_components`, Spatie's 3 pivot tables

### 9.3 Content Module Translation Tables

| Module | Translation Table | FK Column | FK Constraint |
|--------|-------------------|-----------|---------------|
| Article | `content_article_translation` | `article_translation_parent_id` | Application-level |
| Application | `content_application_translations` | `application_translation_parent_id` | Application-level |
| Slider | `content_slider_translation` | `slider_translation_parent_id` | Application-level |
| Video | `content_video_translation` | `video_translation_parent_id` | Application-level |
| Photo Gallery | `content_photo_gallery_translations` | `gallery_translation_parent_id` | Named FK: `fk_gallery_trans_parent` (CASCADE) |
| Calendar | `content_calendar_translations` | `calendar_translation_parent_id` | Named FK: `fk_calendar_trans_parent` (CASCADE) |

### 9.4 Self-Referencing Translation Tables

| Module | Self-Ref FK Column | Parent Condition |
|--------|--------------------|------------------|
| Download | `download_parent_id` | NULL or empty = parent |
| Image | `image_parent_id` | NULL or empty = parent |
| Photo List | `photo_parent_id` | NULL or empty = parent |

---

## 10. Routes

### 10.1 Route Files

| File | Contents |
|------|----------|
| `routes/web.php` | All application routes (admin, user, frontend) |
| `routes/console.php` | Artisan commands (inspire) |
| No `routes/api.php` | API routes not used |

### 10.2 Route Groups

#### Admin Guest Routes (`prefix: admin`, `middleware: guest:admin`)
```
GET  /admin/login                    → admin.login
POST /admin/login                    → admin.login.submit (throttle:5,1)
GET  /admin/forgot-password          → admin.password.request
POST /admin/forgot-password          → admin.password.email
GET  /admin/reset-password/{token}   → admin.password.reset
POST /admin/reset-password           → admin.password.update
```

#### Admin Auth Routes (`prefix: admin`, `middleware: auth:admin`)
```
POST /admin/logout                         → admin.logout
GET  /admin/home                           → admin.home
Resource: /admin/backend-user/*            → backend-user.*
Resource: /admin/frontend-user/*           → frontend-user.*
Resource: /admin/ref/*                     → ref.*
Resource: /admin/content-application/*     → content-application.*
Resource: /admin/content-article/*         → content-article.*
Resource: /admin/content-download/*        → content-download.*
Resource: /admin/content-slider/*          → content-slider.*
Resource: /admin/content-video/*           → content-video.*
Resource: /admin/content-image/*           → content-image.*
Resource: /admin/content-photo-gallery/*   → content-photo-gallery.*
Resource: /admin/content-photo-list/*      → content-photo-list.*
Resource: /admin/content-public-holiday/*  → content-public-holiday.*
Resource: /admin/content-calendar/*        → content-calendar.*
Resource: /admin/roles/*                   → roles.*
Resource: /admin/permissions/*             → permissions.*
Resource: /admin/auto-permissions/*        → auto-permissions.*
Resource: /admin/visitor/*                 → visitor.*
Resource: /admin/data-dashboard/*          → data-dashboard.*
Resource: /admin/backend-menu/*            → backend-menu.*
Resource: /admin/frontend-menu/*           → frontend-menu.*
Resource: /admin/menu-category/*           → menu-category.*
GET  /admin/activity-log                   → activity-log.index
GET  /admin/file-manager                   → file-manager.index
Resource: /admin/routes/*                  → admin.routes.*
Resource: /admin/frontend-site/*           → frontend-site.* (includes components + pages)
```

#### Frontend User Guest Routes (`prefix: user`)
```
GET  /register                    → redirect → /{authSiteSlug}/register-web
POST /register                    → user.register.submit
GET  /login                       → redirect → /{authSiteSlug}/login-web
POST /login                       → user.login.submit
GET  /forgot-password             → redirect → /{authSiteSlug}/forgot-password-web
POST /forgot-password             → user.password.email
GET  /reset-password/{token}      → redirect → /{authSiteSlug}/reset-password-web
POST /reset-password              → user.password.update
```

#### Frontend User Auth Routes (`prefix: user`, `middleware: auth:user`)
```
POST /user/logout                         → user.logout
GET  /email/verify                        → redirect → /{authSiteSlug}/email-verify
POST /email/verification-notification     → user.verification.resend
```

#### CMS Builder Frontend Routes (`middleware: visitor.log`)
```
GET  /                                    → home (default site)
GET  /{slug}                              → frontend-site.home (site home page)
GET  /{site}/{slug}                       → frontend-site.page (site page / article / gallery)
```

---

## 11. Permissions & Roles

### 11.1 Guard Separation

| Guard | Models | Role Examples | Permission Examples |
|-------|--------|---------------|-------------------|
| `admin` | `BackendUser` | `super-admin`, `admin` | `content-article.view`, `backend-user.create` |
| `user` | `FrontendUser` | — | — |

### 11.2 Auto-Permission Generation

`AutoPermissionController` + `ControllerScanner`:
1. Scans all controller methods recursively
2. Generates permissions in format: `{module}.{action}` (e.g., `content-article.create`)
3. Stores in Spatie `permissions` table with guard_name = `admin`
4. Can generate missing permissions on demand

### 11.3 Permission Naming Convention

```
{route-prefix}.{action}
```
Examples: `content-article.view`, `content-article.create`, `content-article.update`, `content-article.delete`

### 11.4 Role Assignment

Both BackendUser and FrontendUser use Spatie's `HasRoles` trait:
```php
$user->assignRole('admin');
$user->hasPermissionTo('content-article.create');
```

---

## 12. Frontend Rendering Pipeline

### 12.1 Request Resolution (`FrontendSiteRenderController::resolve()`)

```
GET /{slug}
    → Check if slug is a CMS site (from cache)
        → YES → render site default page via PortalHandler::handle(slug)
        → NO  → fallback to HomeController@contentArticle (legacy)

GET /{site}/{subSlug}
    → Check if site exists in cache
        → NO  → 404
        → YES → check if subSlug is a FrontendPage
            → YES → PortalHandler::handle(site, subSlug)
            → NO  → 404 (no auto-render for article/gallery)
```

### 12.2 PortalHandler Variables Flow

```
PortalHandler::handle()
    ├── Loads $siteSlug, $siteName from cache
    ├── Queries RoleMapping → $menus (flat), $menuTree (nested)
    ├── Passes all as $vars to:
    │   ├── Entry script (page.content) → Blade::render($vars)
    │   ├── Each component → Blade::render($vars) → stored as $vars['{code}']
    │   ├── Page layout → uses $vars['{code}'] variables
    │   ├── Header component → Blade::render($vars)
    │   ├── Footer component → Blade::render($vars)
    │   └── Site layout → uses $vars['__header__'], $vars['__page__'], $vars['__footer__']
    └── Returns final HTML string
```

### 12.3 Module Detail Pages (Query Param Pattern)

All content module detail (Article, Video, Gallery) is rendered via **page component with query param**:

```
/ {siteSlug} / {pageSlug} ? id = {code}
    → PortalHandler::handle(siteSlug, pageSlug)  -- render page biasa
    → Component dalam page query request()->query('id')
    → Filter ContentArticle / ContentVideo / ContentPhotoGallery by {code}
    → Display single item detail

Example:  / new-site / articles ? id = pelancaran-2026
    → "articles" = page slug
    → Component dalam page query article by article_code = "pelancaran-2026"
    → Render detail HTML
```

**Other modules (Slider, Video, Download, Image, Application, Photo List)** do NOT have detail page handlers. They only appear via `mod_*` list components within a page.

---

## 13. Helpers & Utilities

### 13.1 MenuHelper (`app/Http/Helpers/MenuHelper.php`)
- Backend admin sidebar menu rendering
- Role-based menu caching
- Builds nested menu tree for admin sidebar
- Provides route list for dropdown selection

### 13.2 DashboardHelper (`app/Helpers/DashboardHelper.php`)
- Renders Blade widget content from `DataDashboard` DB records
- Uses `Blade::render()` to process stored Blade templates

### 13.3 SettingHelper (`app/Http/Helpers/SettingHelper.php`)
- Provides `authSiteSlug()` — resolves current site slug from request input, URL segment, or default site
- Provides `defaultSiteSlug()` — returns the default site slug from DB (used in notification/queue contexts)
- Core to the CMS auth system for dynamic site-aware redirects

### 13.4 ActivityObserver (`app/Observers/ActivityObserver.php`)
- Observes Spatie Activitylog `activity_log` table
- Enriches log entries with: `url`, `ip_address`, `user_agent`
- Detects guard (admin/user) from authenticated user

---

## 14. Directory Structure Reference

### 14.1 App Directory

```
app/
├── Helpers/
│   └── DashboardHelper.php
├── Http/
│   ├── Controllers/
│   │   ├── Controller.php                         (abstract base)
│   │   ├── FrontendSiteRenderController.php       (CMS site resolver)
│   │   ├── Backend/                               (25+ admin controllers)
│   │   └── frontendUser/                          (4 user panel controllers)
│   ├── Helpers/                                   (2 helpers)
│   │   ├── MenuHelper.php                         (backend sidebar menu)
│   │   └── SettingHelper.php                      (auth site slug resolution)
│   ├── Middleware/                                 (4 middleware)
│   └── Requests/Backend/                          (17 form requests)
├── Models/
│   ├── User.php
│   ├── Traits/                                     (Visitable, HasVisits)
│   └── Backend/                                    (20+ models)
├── Observers/
│   └── ActivityObserver.php
├── Services/                                       (7 services)
└── View/Composers/                                 (3 composers)
```

### 14.2 Resources Directory

```
resources/views/
├── backend/                   ← Admin panel (TailAdmin theme)
│   ├── auth/                  ← Admin auth pages
│   ├── layouts/               ← Admin layout + sidebar partials
│   ├── module/                ← 20+ module CRUD views
│   └── profile/
├── frontend/                  ← CMS Builder output
│   └── cms.blade.php          ← Single render view
└── vendor/                    ← Published vendor views (elfinder, pagination)
```

### 14.3 Documentation

```
docs/
├── CODEBASE.md                           ← (this file)
├── CMS-AUTH-SYSTEM.md                    ← CMS-based auth pages & dynamic site slug
├── CMS-BUILDER-GUIDE.md                 ← CMS Builder usage guide
├── FRONTEND-COMPONENT-GUIDE.md           ← Component usage guide
├── GLOBAL-SEARCH-DARK-MODE.md            ← Global search & dark mode docs
├── INSTALLATION-SETUP.md                 ← Installation & setup guide
├── MODULE-REFERENCE.md                   ← Module reference index
├── TRANSLATION-GUIDE.md                  ← Translation guide
├── ROUTE-MANAGEMENT.md                   ← Route management docs
├── VISITOR-COUNTER.md                    ← Visitor tracking docs
├── ICON-SYSTEM.md                        ← Icon system guide
├── grapesjs-integration.md              ← GrapesJS visual builder integration
├── frontend-site/                        ← Frontend site builder docs
│   ├── README.md
│   ├── architecture.md
│   ├── composers.md
│   ├── fields.md
│   ├── menu-module-views.md
│   ├── navbar-dynamic-menu.md
│   ├── seed-public-menu.php
│   └── setup.md
└── module/                               ← Individual module docs
    ├── activity-log.md
    ├── auto-permission.md
    ├── backend-user.md
    ├── content-overview.md
    ├── data-dashboard-system.md
    ├── file-manager.md
    ├── frontend-site.md
    ├── frontend-user.md
    ├── menu.md
    ├── permission.md
    ├── ref.md
    ├── role.md
    ├── routes.md
    └── visitor.md
```

---
