# Project layout

Source: https://docs.qiick.io/externa/project-layout

externa-core follows a conventional Laravel application layout with domain folders for Actions, AI, Jobs, and Services, plus an Inertia React UI under `resources/js`. This page is a map of where to look when you change behavior.

## Top-level tree

```bash
externa-core/
├── app/                 # PHP application code
├── bootstrap/           # app.php, providers, cache
├── config/              # Laravel + Externa config
├── database/            # migrations, factories, seeders
├── public/              # web root, built assets, storage link
├── resources/
│   ├── css/
│   ├── js/              # React / Inertia / Wayfinder
│   └── views/           # Blade shell for Inertia
├── routes/              # web + domain route files
├── storage/             # logs, app files, frameworks, zips
├── tests/               # Pest Feature / Unit / Browser
├── artisan
├── composer.json
├── package.json
└── vite.config.*        # Vite + Wayfinder plugin
```

Ignore vendor tooling directories (`.agents`, `.cursor`, etc.) unless you are working on AI coding assistants for the repo.

## `app/` — backend domains

| Path                 | Role                                                                                  |
| -------------------- | ------------------------------------------------------------------------------------- |
| `app/Actions/`       | Single-purpose action classes (e.g. Fortify-related actions under `Actions/Fortify`). |
| `app/Ai/`            | Agent, tools, and AI support code for laravel/ai.                                     |
| `app/Concerns/`      | Shared PHP traits/concerns used across layers.                                        |
| `app/Console/`       | Artisan commands (cleanup, sync, AI sync sources, …).                                 |
| `app/Enums/`         | `PermissionEnum`, `RoleEnum`, `FieldTypeEnum`, `FileTypeEnum`, etc.                   |
| `app/Http/`          | Controllers, middleware, form requests, API resources.                                |
| `app/Jobs/`          | Queued work: duplicate files, prepare zips, import collections.                       |
| `app/Models/`        | Eloquent models.                                                                      |
| `app/Notifications/` | In-app / mail notifications.                                                          |
| `app/Providers/`     | Service providers.                                                                    |
| `app/Services/`      | Domain services (files, collections, authorization).                                  |
| `app/Support/`       | Small helpers and support types.                                                      |
| `app/Traits/`        | Model or cross-cutting traits.                                                        |

### Models (`app/Models`)

Core domain models include:

- `User`, `UserGroup`
- `Collection`, `CollectionField`, `CollectionItem`, `CollectionItemValue`
- `File`, `FileUpload`, `FileVersion`
- `AiChatAttachment`, `AiSyncSource`

Spatie models (roles, permissions, activity, tags) are provided by packages; application code references them via config and enums.

### HTTP layer (`app/Http`)

Controllers are grouped by product area:

```bash
app/Http/Controllers/
├── DashboardController.php
├── NotificationController.php
├── Admin/
│   ├── UserController.php
│   ├── UserGroupController.php
│   ├── RoleController.php
│   ├── PermissionController.php
│   ├── ActivityLogController.php
│   └── FileController.php
├── Collections/
│   ├── ContentCollectionController.php
│   ├── FieldController.php
│   └── ItemController.php
├── Ai/
│   ├── AiPageController.php
│   ├── AiChatController.php
│   ├── AiConversationController.php
│   ├── AiChatAttachmentController.php
│   ├── AiStatusController.php
│   ├── ImportJobStatusController.php
│   └── CollectionImportWebhookController.php
└── Settings/
    ├── ProfileController.php
    └── SecurityController.php
```

Form requests mirror those areas under `app/Http/Requests/{Admin,Ai,Collections,Settings,Notifications}`. JSON/Inertia resources live under `app/Http/Resources`.

Middleware of note:

- `HandleInertiaRequests` — shared props for the React shell
- `EnsureUserHasPermission` / `EnsureCanManageFiles` — permission gates beyond route middleware
- `HandleAppearance` — UI appearance preferences

Route middleware also uses Spatie’s `permission:` alias extensively in the route files.

### Services (`app/Services`)

| Path                                                     | Typical responsibility                                   |
| -------------------------------------------------------- | -------------------------------------------------------- |
| `Services/FileService.php`                               | File tree operations used by controllers/jobs.           |
| `Services/FileTransformService.php`                      | Transforms / derived file handling.                      |
| `Services/Authorization/EffectivePermissionResolver.php` | Merges direct + group-inherited permissions/roles.       |
| `Services/Collections/`                                  | Collection/item domain logic extracted from controllers. |

Prefer extending a service or action over bloating controllers when adding behavior.

### Jobs (`app/Jobs`)

| Job                   | Why it is queued                         |
| --------------------- | ---------------------------------------- |
| `DuplicateFilesJob`   | Folder/bulk/large duplicates.            |
| `PrepareFilesZipJob`  | Async zip under `storage/app/zips`.      |
| `ImportCollectionJob` | CSV/remote imports driven by AI or HTTP. |

Requires `QUEUE_CONNECTION=database` (default) and a running worker — see [Installation](https://docs.qiick.io/externa/installation).

### AI (`app/Ai`)

```bash
app/Ai/
├── Agents/
│   └── AppAssistant.php          # Primary in-app assistant
├── Tools/                        # Tool classes invoked by the agent
│   ├── ManageCollections.php
│   ├── ManageCollectionItems.php
│   ├── QueryCollectionItems.php
│   ├── SearchSimilarCollectionItems.php
│   ├── ExportCollection.php
│   ├── ImportCollectionCsv.php
│   ├── ImportRemoteJson.php
│   ├── GetImportJobStatus.php
│   ├── ManageFiles.php
│   ├── ManageUsers.php
│   ├── ManageGroups.php
│   ├── ManageRoles.php
│   ├── ManageAiSyncSources.php
│   ├── QueryActivityLogs.php
│   ├── RollbackLastAiTurn.php
│   └── ExtractPdfText.php
├── Concerns/                     # Permissions, import helpers, logging
└── Support/                      # URL validation, JSON decode, activity helpers
```

Tools should keep using `ChecksAiPermissions` (and related concerns) so the assistant cannot outrank the signed-in user. Product docs: [AI assistant model](https://docs.qiick.io/externa/ai-assistant).

### Enums (`app/Enums`)

Source of truth for seeded roles/permissions and field/file typing. Changing `PermissionEnum` usually means running the sync/seed path so the database matches — see console commands under `app/Console/Commands` (e.g. permission sync).

## Routes (`routes/`)

Application HTTP routes are split by domain and composed from `web.php`:

| File                     | Contents                                                                     |
| ------------------------ | ---------------------------------------------------------------------------- |
| `routes/web.php`         | Home redirect, dashboard, notifications; `require`s the files below.         |
| `routes/admin.php`       | Users, groups, roles, permissions, activity logs, file manager.              |
| `routes/settings.php`    | Profile / security (Fortify-backed settings UI).                             |
| `routes/collections.php` | Collections, fields, items, **item chat aliases**.                           |
| `routes/chat.php`        | Team chat hub pages + JSON (item + direct threads).                          |
| `routes/ai.php`          | AI pages, chat, conversations, attachments, status, webhooks, import status. |
| `routes/console.php`     | Schedule / console route definitions.                                        |

Authenticated groups typically use `auth` + `verified`, then per-route `permission:…` middleware with `PermissionEnum` values.

> **Note**
> When adding a feature, add the route in the matching domain file, a controller method, a form request if input is non-trivial, and an Inertia page under `resources/js/pages/…`. Generate or refresh Wayfinder types so the frontend can call named routes safely.

## Config (`config/`)

Externa-specific and heavily used package configs:

| File                     | Purpose                                                                                                 |
| ------------------------ | ------------------------------------------------------------------------------------------------------- |
| `config/ai.php`          | Default provider, local gateway, embeddings/MCP flags, remote import hosts, daily limit, webhook token. |
| `config/collections.php` | Seed/fallback for content locales until project settings are saved.                                     |
| `config/files.php`       | Duplicate sync threshold, zip TTL and max size.                                                         |
| `config/super_admin.php` | Initial super admin for `CreateSuperAdminSeeder`.                                                       |
| `config/permission.php`  | Spatie permission package.                                                                              |
| `config/fortify.php`     | Auth features / Fortify.                                                                                |
| `config/activitylog.php` | Spatie activity log enablement and buffer.                                                              |

Standard Laravel configs (`app.php`, `database.php`, `queue.php`, `session.php`, `cache.php`, `mail.php`, `filesystems.php`, …) behave as usual.

## Frontend (`resources/js/`)

| Path                     | Role                                                             |
| ------------------------ | ---------------------------------------------------------------- |
| `pages/`                 | Inertia page components (one route → one page module).           |
| `components/`            | Shared UI (shell, sidebar, admin/collections/ai widgets, `ui/`). |
| `hooks/`                 | React hooks.                                                     |
| `lib/`                   | Client utilities.                                                |
| `layouts/`               | App layouts.                                                     |
| `interfaces/` / `types/` | TypeScript types.                                                |
| `enums/`                 | Frontend enum mirrors where needed.                              |
| `actions/`               | Client-side action helpers when used by the starter patterns.    |
| `routes/` + `wayfinder/` | **Generated** Wayfinder TypeScript for Laravel routes/actions.   |
| `app.tsx` / `ssr.tsx`    | Inertia app entry (CSR / SSR).                                   |

### Page map

```bash
resources/js/pages/
├── dashboard.tsx
├── welcome.tsx
├── auth/                 # Login, register, passwords, 2FA, …
├── settings/             # Profile, security, appearance
├── admin/
│   ├── users/
│   ├── groups/
│   ├── roles/
│   ├── permissions/
│   ├── files/
│   └── activity-logs/
├── collections/
│   ├── collections/
│   └── items/
└── ai/                   # Chat / assistant UI
```

Component folders often mirror these domains (`components/admin`, `components/collections`, `components/ai`, …).

### Wayfinder

Externa uses **Laravel Wayfinder** with `@laravel/vite-plugin-wayfinder`. Named routes and controller actions are exposed as typed helpers under `resources/js` (commonly `resources/js/routes` and `resources/js/wayfinder`, generated on build/dev).

Workflow:

1. Define or change a route in `routes/*.php`.
2. Run Vite (`npm run dev` / `composer run dev`) or the project’s Wayfinder generation step so TS clients update.
3. Import the generated helper in the page/component instead of hard-coding URL strings.

> Do not hand-edit generated Wayfinder output. Change PHP routes/controllers and regenerate.

## Database (`database/`)

```bash
database/
├── migrations/     # Schema (users, permissions, collections, files, AI, jobs, …)
├── factories/      # Model factories for tests
└── seeders/
    ├── DatabaseSeeder.php
    ├── PermissionSeeder.php
    ├── RoleSeeder.php
    └── CreateSuperAdminSeeder.php
```

Seed order: permissions → roles → super admin. See [Installation](https://docs.qiick.io/externa/installation).

## Storage and public assets

| Location             | Use                                                                             |
| -------------------- | ------------------------------------------------------------------------------- |
| `storage/app/`       | Private app files; prepared zips under a zips directory per `config/files.php`. |
| `storage/app/public` | Public disk files; must be linked via `php artisan storage:link`.               |
| `public/build`       | Vite-built assets after `npm run build`.                                        |

## Tests (`tests/`)

Pest 4 layout:

```bash
tests/
├── Pest.php
├── TestCase.php
├── Feature/
│   ├── Admin/
│   ├── Ai/
│   ├── Auth/
│   ├── Authorization/
│   ├── Collections/
│   ├── Settings/
│   ├── DashboardTest.php
│   ├── NotificationsTest.php
│   └── …
├── Unit/
│   └── Collections/   # and other unit suites
└── Browser/
    └── FileManagerBrowserTest.php   # Playwright-backed browser tests
```

Run:

```bash
php artisan test
composer test          # clears config, runs Pint check, then tests
composer ci:check      # lint/format/types + tests (heavier)
```

Mirror feature folders when adding coverage (e.g. new AI tool → `tests/Feature/Ai/…`).

## Console commands (`app/Console/Commands`)

Operational commands include (names may be prefixed — check `php artisan list`):

- Permission sync from enums
- Cleanup expired file zips
- Cleanup stale file uploads
- Cleanup AI chat attachments
- Run AI sync sources

Scheduling, if any, is wired through `routes/console.php` / the application scheduler — see [Queues & scheduler](https://docs.qiick.io/externa/operations).

## Where to change what (cheat sheet)

| You want to…                 | Start here                                                          |
| ---------------------------- | ------------------------------------------------------------------- |
| Add an admin HTTP endpoint   | `routes/admin.php` → `Http/Controllers/Admin` → `pages/admin/…`     |
| Change collection validation | `Http/Requests/Collections` + enums/services                        |
| Adjust effective permissions | `EffectivePermissionResolver` + policies/middleware + seed enums    |
| Add an AI capability         | New class in `app/Ai/Tools` registered on `AppAssistant`            |
| Tune zip/duplicate limits    | `config/files.php` / `FILES_*` env                                  |
| Change default AI provider   | `config/ai.php` / `AI_*` and `LOCAL_AI_*`                           |
| Alter first seeded login     | `config/super_admin.php` / `INITIAL_SUPER_ADMIN_*`                  |
| Fix shared Inertia props     | `HandleInertiaRequests`                                             |
| Add a queued workflow        | `app/Jobs` + ensure worker in [Installation](https://docs.qiick.io/externa/installation) |

## Related docs

- [Architecture](https://docs.qiick.io/externa/architecture) — request lifecycle and layering
- [Environment variables](https://docs.qiick.io/externa/environment-variables) — `.env` reference
- [Routing overview](https://docs.qiick.io/externa/routing) — HTTP surface summary
- [Testing](https://docs.qiick.io/externa/testing) — conventions for Pest and browser tests
