# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

A **headless CMS backoffice** built on Laravel 8 (PHP 8.1+), originally started on Laravel 4 and gradually updated. The admin UI is server-rendered Blade; the public frontend is a separate app (`efet-frontend`) consuming the REST API. All code (variables, comments, class names) must be written in **English**.

## Commands

```bash
# Install dependencies
composer install
yarn install        # or: lando composer install

# Run dev server (Lando-based)
lando start

# Database setup (migrates + seeds)
lando prep-db
# Or manually:
php artisan migrate && php artisan migrate --path="/database/migrations/project" && php artisan db:seed

# Asset compilation
yarn dev            # development build
yarn watch          # watch mode
yarn prod           # production build

# Tests (Pest)
php artisan test                        # all tests
php artisan test --filter=DocumentTest  # single test class
php artisan test --coverage             # with coverage

# Browser tests (Dusk) — WARNING: erases DB, run lando prep-db after
lando test-dusk
```

## Architecture

### Config-Driven Content Types

The core pattern: content types are defined declaratively in `config/backoffice/generic/` as PHP arrays specifying fields, validation, labels, sort/search options. A single `ContentController` handles all CRUD for all generic content types via a `{type}` route parameter. The `ContentTypes` facade and `BackofficeService` orchestrate this.

Custom content types (that don't fit the generic structure) require manual controllers, views, and logic — they have no fixed pattern.

### The `scopeFull()` Query Pattern

`ContentTypeModel::scopeFull()` is the central query strategy. It performs a **triple join** in one SQL query:
1. The content's `_languages` table (filtered to current locale)
2. `website_objects` (polymorphic content registry)
3. `website_objects_lang` (localized slugs/URIs)

Usage: `Document::full()->where('status', 'published')->get()`. This replaces lazy loading with a single optimized query. All content models extend `ContentTypeModel`.

### Multi-Language Convention

Every translatable content type has a companion `_languages` table (e.g., `documents` → `document_languages`). The `LanguageSupportTrait` overrides `getAttribute()` so `$model->title` transparently returns the correct locale's value. Language models extend `TranslationModel`.

### `website_objects` — The Polymorphic Spine

All content types register in `website_objects` (with `object_id`, `object_type`, `content_type`, `slug`, `status`). The `status` column (`draft`/`published`) gates what the frontend API returns. `website_objects_lang` holds per-locale slugs/URIs.

### Model Hierarchy

- `BaseModel` → base Eloquent model with `BaseCollection`
- `TranslationModel` extends `BaseModel` → for `*Language` models
- `ContentTypeModel` extends `BaseModel` → for all CMS content models (provides `scopeFull`, `scopeWebsite`)

### API Layer

- **Public API** (`routes/api.php`): versioned under `/v1`, read-only. One controller per resource, uses Laravel `JsonResource` classes in `app/Http/Resources/`.
- **Admin API**: uses Fractal transformers in `app/Libs/Transformers/` for backend-only resources (users, permissions, form responses).

### Key Directories

- `app/Libs/` — core CMS engine (ContentTypes, BackofficeService, Transformers, Services)
- `app/Helpers/` — six autoloaded global helper files (language, media, html, database, path, string)
- `app/Facades/` — facades wrapping Lib singletons
- `config/backoffice/generic/` — content type configuration files (~27 types)
- `config/backoffice.php` — admin menu structure and global settings

### Database Migrations

- `database/migrations/` — core framework tables (users, media, website_objects, menus, entries)
- `database/migrations/project/` — **all new project migrations go here**

### Tests

- `tests/Feature/Api/` — API endpoint tests (~18 test classes, one per resource)
- `tests/Browser/` — Laravel Dusk E2E tests for admin UI
- `tests/Unit/` — minimal unit test coverage
- Test runner: Pest PHP
- CI: tests run on push to `staging` via Bitbucket Pipelines

## Conventions

- Admin panel accessed at `{APP_URL}/admin`
- Route pattern for generic content: `admin/content/{type}` where `{type}` maps to a config file in `config/backoffice/generic/`
- When creating new content types: add config in `config/backoffice/generic/`, create model + language model, add migration in `database/migrations/project/`
- `Model::full()` is the standard way to query content with translations and website object data
